From 1dfcf04f843055bd58d1c0460bf8fdca413d8634 Mon Sep 17 00:00:00 2001 From: Binaek Sarkar Date: Sat, 21 Feb 2026 00:03:27 +0530 Subject: [PATCH 01/21] Refactor documentation structure and content for clarity and completeness This commit introduces significant updates to the Sentrie documentation, enhancing the organization and accuracy of information across various sections. **Key Changes:** - **Getting Started Section**: - Replaced outdated entries with new content, including an introduction to Sentrie and a quick start guide for installation and policy evaluation. - **Language Concepts**: - Added comprehensive documentation on pattern matching, conditionals, and the type system, including shapes and constraints. - Introduced a new guide on policy composition, detailing the export and import of rules. - **Reference Documentation**: - Updated arithmetic and boolean operations documentation to reflect current syntax and examples. - Created a new CLI reference page detailing commands and their parameters. - **Extensibility**: - Added documentation for writing custom TypeScript modules, including syntax and usage examples. - **Deployment Operations**: - Introduced new pages for running Sentrie as a service and CLI reference, detailing commands and expected behaviors. **Benefits:** - Improved clarity and usability of documentation for new and existing users. - Enhanced organization of content for easier navigation and understanding. - Comprehensive coverage of new features and concepts introduced in recent updates. ## Testing - Documentation reviewed for accuracy and consistency. - All new pages and updates verified for correct rendering and functionality. ## Dependencies - None - documentation-only changes. --- astro.config.mjs | 252 +---- docs-template.md | 44 + .../deployment-operations/cli-reference.md | 60 ++ .../running-as-service.md | 131 +++ .../writing-custom-typescript-modules.md | 58 ++ .../docs/getting-started/introduction.md | 56 + .../docs/getting-started/quick-start.md | 108 ++ src/content/docs/index.mdx | 52 +- .../pattern-matching-conditionals.md | 67 ++ .../language-concepts/policy-composition.md | 123 +++ .../language-concepts/type-system-shapes.md | 78 ++ .../docs/reference/arithmetic-operations.md | 146 +-- .../docs/reference/boolean-operations.md | 642 +----------- .../docs/reference/collection-operations.md | 958 +----------------- src/content/docs/reference/constraints.md | 129 +-- src/content/docs/reference/facts.md | 252 +---- src/content/docs/reference/functions.md | 399 +------- src/content/docs/reference/index.md | 876 +--------------- src/content/docs/reference/let.md | 513 +--------- src/content/docs/reference/namespaces.md | 75 +- src/content/docs/reference/policies.md | 483 +-------- src/content/docs/reference/precedence.md | 120 +-- src/content/docs/reference/rules.md | 204 +--- .../reference/security-and-permissions.md | 53 +- src/content/docs/reference/shapes.md | 417 +------- src/content/docs/reference/trinary.md | 244 +---- .../docs/reference/types-and-values.md | 127 +-- .../reference/typescript_modules/index.md | 399 +------- .../typescript_modules/sentrie/collection.md | 8 + .../typescript_modules/sentrie/crypto.md | 8 +- .../typescript_modules/sentrie/encoding.md | 8 + .../typescript_modules/sentrie/hash.md | 11 +- .../typescript_modules/sentrie/js.md | 8 + .../typescript_modules/sentrie/json.md | 78 +- .../typescript_modules/sentrie/jwt.md | 8 + .../typescript_modules/sentrie/net.md | 8 + .../typescript_modules/sentrie/regex.md | 8 + .../typescript_modules/sentrie/semver.md | 8 + .../typescript_modules/sentrie/time.md | 8 + .../typescript_modules/sentrie/url.md | 8 + .../typescript_modules/sentrie/uuid.md | 138 +-- 41 files changed, 1499 insertions(+), 5874 deletions(-) create mode 100644 docs-template.md create mode 100644 src/content/docs/deployment-operations/cli-reference.md create mode 100644 src/content/docs/deployment-operations/running-as-service.md create mode 100644 src/content/docs/extensibility/writing-custom-typescript-modules.md create mode 100644 src/content/docs/getting-started/introduction.md create mode 100644 src/content/docs/getting-started/quick-start.md create mode 100644 src/content/docs/language-concepts/pattern-matching-conditionals.md create mode 100644 src/content/docs/language-concepts/policy-composition.md create mode 100644 src/content/docs/language-concepts/type-system-shapes.md diff --git a/astro.config.mjs b/astro.config.mjs index 3e5a8db..96e4a56 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -26,235 +26,69 @@ export default defineConfig({ { label: "Getting Started", items: [ - { - label: "What is Sentrie?", - slug: "getting-started/what-is-sentrie", - }, - { - label: "What is Policy as Code?", - slug: "getting-started/what-is-policy-as-code", - }, - { - label: "Why Sentrie?", - slug: "getting-started/why-sentrie", - }, - { - label: "Installation", - slug: "getting-started/installation", - }, - { - label: "Writing your first Policy", - slug: "getting-started/writing-your-first-policy", - }, - { - label: "Running your Policy", - slug: "getting-started/running-your-policy", - }, - { - label: "Policy Enforcement", - slug: "getting-started/enforcement", - }, + { label: "Introduction & Core Philosophy", slug: "getting-started/introduction" }, + { label: "Quick Start", slug: "getting-started/quick-start" }, ], }, { - label: "Structure of a Policy Pack", + label: "Language Concepts", items: [ - { - label: "Overview", - slug: "structure-of-a-policy-pack/overview", - }, - { - label: "Pack File", - slug: "structure-of-a-policy-pack/packfile", - }, - { - label: "Program File", - slug: "structure-of-a-policy-pack/program-file", - }, - { - label: "TypeScript File", - slug: "structure-of-a-policy-pack/typescript-file", - }, - { - label: "Example Pack", - slug: "structure-of-a-policy-pack/example-pack", - }, + { label: "Type System & Shapes Overview", slug: "language-concepts/type-system-shapes" }, + { label: "Policy Composition", slug: "language-concepts/policy-composition" }, + { label: "Pattern Matching & Conditionals", slug: "language-concepts/pattern-matching-conditionals" }, ], }, { - label: "Running Sentrie", + label: "Language Reference", items: [ - { - label: "Executing Policies", - slug: "running-sentrie/executing-policies", - }, - { - label: "Serving Policies", - slug: "running-sentrie/serving-policies", - }, + { label: "Overview", slug: "reference" }, + { label: "Namespaces", slug: "reference/namespaces" }, + { label: "Policies", slug: "reference/policies" }, + { label: "Rules", slug: "reference/rules" }, + { label: "Facts", slug: "reference/facts" }, + { label: "Intermediate Values (let)", slug: "reference/let" }, + { label: "Types and Values", slug: "reference/types-and-values" }, + { label: "Constraints", slug: "reference/constraints" }, + { label: "Trinary Values", slug: "reference/trinary" }, + { label: "Shapes", slug: "reference/shapes" }, + { label: "Arithmetic Operations", slug: "reference/arithmetic-operations" }, + { label: "Boolean Operations", slug: "reference/boolean-operations" }, + { label: "Collection Operations", slug: "reference/collection-operations" }, + { label: "Functions", slug: "reference/functions" }, + { label: "Precedence", slug: "reference/precedence" }, + { label: "Security and Permissions", slug: "reference/security-and-permissions" }, ], }, { - label: "Reference", + label: "TypeScript Modules", items: [ - { - label: "Overview", - slug: "reference", - }, - { - label: "Types and Values", - slug: "reference/types-and-values", - }, - { - label: "Constraints", - slug: "reference/constraints", - }, - { - label: "Trinary Values", - slug: "reference/trinary", - }, - { - label: "Shapes", - slug: "reference/shapes", - }, - { - label: "Namespaces", - slug: "reference/namespaces", - }, - { - label: "Policies", - slug: "reference/policies", - }, - { - label: "Facts", - slug: "reference/facts", - }, - { - label: "Rules", - slug: "reference/rules", - }, - { - label: "Exporting and Importing Rules", - slug: "reference/exporting-and-importing-rules", - }, - { - label: "Intermediate Values", - slug: "reference/let", - }, - { - label: "Arithmetic Operations", - slug: "reference/arithmetic-operations", - }, - { - label: "Boolean Operations", - slug: "reference/boolean-operations", - }, - { - label: "Collection Operations", - slug: "reference/collection-operations", - }, - { - label: "Using Functions", - slug: "reference/functions", - }, - { - label: "Using TypeScript", - slug: "reference/using-typescript", - }, - { - label: "Precedence", - slug: "reference/precedence", - }, - { - label: "Security and Permissions", - slug: "reference/security-and-permissions", - }, + { label: "Overview", slug: "reference/typescript_modules" }, + { label: "JavaScript Globals", slug: "reference/typescript_modules/sentrie/js" }, + { label: "Collection", slug: "reference/typescript_modules/sentrie/collection" }, + { label: "Crypto", slug: "reference/typescript_modules/sentrie/crypto" }, + { label: "Encoding", slug: "reference/typescript_modules/sentrie/encoding" }, + { label: "Hash", slug: "reference/typescript_modules/sentrie/hash" }, + { label: "JSON", slug: "reference/typescript_modules/sentrie/json" }, + { label: "JWT", slug: "reference/typescript_modules/sentrie/jwt" }, + { label: "Net", slug: "reference/typescript_modules/sentrie/net" }, + { label: "Regex", slug: "reference/typescript_modules/sentrie/regex" }, + { label: "Semver", slug: "reference/typescript_modules/sentrie/semver" }, + { label: "Time", slug: "reference/typescript_modules/sentrie/time" }, + { label: "URL", slug: "reference/typescript_modules/sentrie/url" }, + { label: "UUID", slug: "reference/typescript_modules/sentrie/uuid" }, ], }, { - label: "TypeScript Modules", + label: "Extensibility", items: [ - { - label: "Overview", - slug: "reference/typescript_modules", - }, - { - label: "JavaScript Globals", - slug: "reference/typescript_modules/sentrie/js", - }, - { - label: "Collection", - slug: "reference/typescript_modules/sentrie/collection", - }, - { - label: "Crypto", - slug: "reference/typescript_modules/sentrie/crypto", - }, - { - label: "Encoding", - slug: "reference/typescript_modules/sentrie/encoding", - }, - { - label: "Hash", - slug: "reference/typescript_modules/sentrie/hash", - }, - { - label: "JSON", - slug: "reference/typescript_modules/sentrie/json", - }, - { - label: "JWT", - slug: "reference/typescript_modules/sentrie/jwt", - }, - { - label: "Net", - slug: "reference/typescript_modules/sentrie/net", - }, - { - label: "Regex", - slug: "reference/typescript_modules/sentrie/regex", - }, - { - label: "Semver", - slug: "reference/typescript_modules/sentrie/semver", - }, - { - label: "Time", - slug: "reference/typescript_modules/sentrie/time", - }, - { - label: "URL", - slug: "reference/typescript_modules/sentrie/url", - }, - { - label: "UUID", - slug: "reference/typescript_modules/sentrie/uuid", - }, + { label: "Writing Custom TypeScript Modules", slug: "extensibility/writing-custom-typescript-modules" }, ], }, { - label: "CLI Reference", + label: "Deployment & Operations", items: [ - { - label: "Overview", - slug: "cli-reference", - }, - { - label: "init", - slug: "cli-reference/init", - }, - { - label: "exec", - slug: "cli-reference/exec", - }, - { - label: "serve", - slug: "cli-reference/serve", - }, - { - label: "validate", - slug: "cli-reference/validate", - }, + { label: "CLI Reference", slug: "deployment-operations/cli-reference" }, + { label: "Running as a Service", slug: "deployment-operations/running-as-service" }, ], }, ], diff --git a/docs-template.md b/docs-template.md new file mode 100644 index 0000000..befa024 --- /dev/null +++ b/docs-template.md @@ -0,0 +1,44 @@ +# [Feature / Function Name] + +[A brief, 1-2 sentence introductory paragraph. Write this for the human. Explain what the feature is and the primary reason a developer would reach for it. Keep it factual and avoid marketing fluff, but let it flow naturally.] + +## Syntax + +```typescript +// The formal definition or type signature +``` + +## Parameters + +| Name | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| `paramName` | `Type` | Yes/No | A clear, concise explanation of what this input does. | + +**Returns:** `ReturnType` — [A brief description of the output, e.g., "A boolean indicating whether the constraint was met."] + +## Examples + +### Basic Usage +[One sentence explaining what this example demonstrates.] +```typescript +// Minimum reproducible code block. Use realistic variable names. +``` + +### Advanced Usage +[One sentence explaining the complexity or edge case shown here.] +```typescript +// Example showing composition, complex input, or error handling. +``` + +## Behavior & Constraints + +> **Note:** [Optional: A high-visibility callout for the most critical thing a human needs to know before using this feature.] + +* **[Constraint 1]:** e.g., Failing constraint validation will abort evaluation immediately. +* **[Limitation]:** e.g., Does not support dynamic key generation. All keys must be explicitly declared. +* **[Compatibility]:** e.g., Cannot be used in conjunction with [Feature Y]. + +## Constraints & Edge Cases +* [Limitation 1: e.g., "Failing constraint validation will abort evaluation immediately."] +* [Limitation 2: e.g., "Does not support dynamic key generation."] +* [Mutual Exclusions: e.g., "Cannot be used in conjunction with X."] diff --git a/src/content/docs/deployment-operations/cli-reference.md b/src/content/docs/deployment-operations/cli-reference.md new file mode 100644 index 0000000..3cccd99 --- /dev/null +++ b/src/content/docs/deployment-operations/cli-reference.md @@ -0,0 +1,60 @@ +--- +title: CLI Reference +description: "Sentrie CLI: exec, init, serve, validate. Syntax, options, and behavior." +--- + +# CLI Reference + +The Sentrie CLI provides commands to execute policies, initialize packs, serve an HTTP API, and validate packs. All commands support `--help`, `--version`, `--debug`, `--log-level`. + +## Syntax + +```bash +sentrie exec [OPTIONS] +sentrie init +sentrie serve [OPTIONS] [policy-pack] +sentrie validate [OPTIONS] +``` + +## Parameters + +| Command | Required | Description | +| :--- | :--- | :--- | +| `exec` FQN | Yes | `namespace/policy` or `namespace/policy/rule`. Execute rule(s) with facts. | +| `init` pack-name | Yes | Create a new policy pack in the current directory. | +| `serve` | No | Start HTTP server. Optional positional: pack directory. Default pack: `./`. | +| `validate` FQN | Yes | Validate pack structure and types. | + +**exec options:** `--pack-location` (default `./`), `--facts` (JSON string), `--fact-file` (path), `--output` (`table` \| `json`). + +**serve options:** `--port` (default `7529`), `--pack-location` (default `./`), `--listen` (default `["local"]`: `local` \| `all` \| IP list). + +**validate options:** `--pack-location` (default `./`), `--facts` (JSON for type checking). + +**Returns:** exec: exit 0 with decision output; non-zero on error. serve: runs until SIGINT/SIGTERM. validate: exit 0 if valid. + +## Examples + +### Basic Usage + +```bash +sentrie exec com/example/auth/user/allow --facts '{"user":{"role":"admin"}}' +sentrie serve --port 8080 +sentrie validate com/example/auth/user +``` + +### Advanced Usage + +```bash +sentrie exec com/example/auth/user --fact-file ./facts.json --output json +sentrie serve --listen all --pack-location /path/to/pack +``` + +## Behavior & Constraints + +- Global options: `--help`, `--version`, `--debug`, `--log-level` (DEBUG, INFO, WARN, ERROR). Facts for exec must be valid JSON; keys match policy fact names/aliases. +- serve: Loads pack from `--pack-location` or positional arg; policy/JS errors prevent startup. Graceful shutdown on SIGINT/SIGTERM. + +## Constraints & Edge Cases + +- Missing required fact for exec causes error. Invalid FQN or pack path causes error. Port in use: use another `--port`. See [Running as a Service](/deployment-operations/running-as-service) for HTTP API. diff --git a/src/content/docs/deployment-operations/running-as-service.md b/src/content/docs/deployment-operations/running-as-service.md new file mode 100644 index 0000000..a1e72ad --- /dev/null +++ b/src/content/docs/deployment-operations/running-as-service.md @@ -0,0 +1,131 @@ +--- +title: Running as a Service +description: "HTTP API for policy evaluation: endpoints, request/response schemas, and error format." +--- + +# Running as a Service + +Start the server with `sentrie serve`. The API exposes health and decision evaluation. Request/response are JSON. Errors use RFC 9457 Problem Details. + +## Syntax + +```bash +sentrie serve [ --port 7529 ] [ --pack-location ./ ] [ --listen local | all | IP... ] +``` + +## Endpoints + +### GET /health + +**Returns:** `200` with body: + +```json +{ + "status": "healthy", + "time": "" +} +``` + +### POST /decision/{target...} + +Evaluates a policy or rule. `{target...}` = path segments: `namespace/policy` or `namespace/policy/rule`. + +**Request:** `Content-Type: application/json` + +```json +{ + "facts": { + "": , + ... + } +} +``` + +| Field | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| `facts` | object | Yes | Map of fact name (or alias) to JSON value. Must satisfy policy fact types. | + +**Response:** `200` with body: + +```json +{ + "decisions": [ + { + "policy": "string", + "namespace": "string", + "rule": "string", + "decision": { + "state": "TRUE" | "FALSE" | "UNKNOWN", + "value": + }, + "attachments": { "": , ... }, + "trace": { ... } + } + ], + "error": "string" +} +``` + +| Field | Type | Description | +| :--- | :--- | :--- | +| `decisions` | array | One entry per evaluated rule. | +| `decision.state` | string | Trinary outcome. | +| `decision.value` | any | Rule yield value. | +| `attachments` | object | Exported attachments for that rule. | +| `error` | string | Non-empty if execution failed. | + +## Parameters + +| Endpoint | Method | Path | Body | +| :--- | :--- | :--- | :--- | +| Health | GET | `/health` | none | +| Decision | POST | `/decision/{namespace}/{policy}[/{rule}]` | `{ "facts": { ... } }` | + +**Returns:** See schemas above. 400 for invalid JSON or path; 404 for unknown policy/rule; 405 for wrong method; 500 for evaluation error. + +## Examples + +### Basic Usage + +```bash +curl -X POST "http://localhost:7529/decision/com/example/auth/user/allow" \ + -H "Content-Type: application/json" \ + -d '{"facts":{"user":{"role":"admin"}}}' +``` + +### Advanced Usage + +```bash +curl -X POST "http://localhost:7529/decision/com/example/auth/user" \ + -H "Content-Type: application/json" \ + -d '{"facts":{"user":{"id":"u1","role":"admin"},"context":{}}}' +``` + +## Error Responses (RFC 9457) + +`Content-Type: application/problem+json` + +```json +{ + "type": "https://sentrie.sh/problems/", + "title": "string", + "status": 400 | 404 | 405 | 500, + "detail": "string", + "instance": "string" +} +``` + +| Status | Meaning | +| :--- | :--- | +| 400 | Bad request (invalid JSON, missing path). | +| 404 | Policy or rule not found. | +| 405 | Method not allowed (e.g. GET on /decision). | +| 500 | Internal error during evaluation. | + +## Behavior & Constraints + +- Base URL: default `http://localhost:7529`. `--listen all` binds 0.0.0.0. Only POST is allowed for `/decision/{target...}`. Query parameters are parsed but not used for execution semantics. + +## Constraints & Edge Cases + +- Facts must match policy declarations (required facts present; types/shapes valid). Missing or invalid facts can return 400 or 500. CORS headers are sent (`Access-Control-Allow-Origin: *` etc.). No built-in auth or rate limiting. diff --git a/src/content/docs/extensibility/writing-custom-typescript-modules.md b/src/content/docs/extensibility/writing-custom-typescript-modules.md new file mode 100644 index 0000000..ab689e3 --- /dev/null +++ b/src/content/docs/extensibility/writing-custom-typescript-modules.md @@ -0,0 +1,58 @@ +--- +title: Writing Custom TypeScript Modules +description: "How to add and use your own TypeScript modules in a policy pack: paths, exports, and use statement." +--- + +# Writing Custom TypeScript Modules + +You can add TypeScript files to your policy pack and import them in policies with the `use` statement. Paths are relative to the policy file or use `@local` (pack root). Built-in modules are `@sentrie/*`; local files use quoted relative paths. + +## Syntax + +```text +use { fn1, fn2 } from "./path/to/file.ts" [ as alias ] +use { fn1 } from "@local/path/to/module" [ as alias ] +``` + +Built-in: `use { fn1 } from @sentrie/module` (no quotes). Local: quoted path or `@local/...`; resolved relative to current file or pack root. + +## Parameters + +| Element | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| `fn1`, `fn2` | identifiers | Yes | Exported function (or type) names from the module. | +| `"./file.ts"` or `@local/...` | path | Yes | Relative to `.sentrie` file or pack root. | +| `as alias` | identifier | No | Default alias: last segment of path (e.g. `utils` for `./utils.ts`). | + +**Returns:** N/A (import). Call as `alias.fn1(args)` in the policy. + +## Examples + +### Basic Usage + +```text +use { calculateAge, validateEmail } from "./utils.ts" as utils +rule myrule = default false { + yield utils.calculateAge(user.birthDate) >= 18 and utils.validateEmail(user.email) +} +``` + +### Advanced Usage + +```text +use { calculateAge, validateEmail } from "../helpers/validation.ts" +fact user!: utils.User +yield utils.calculateAge(user.birthDate) >= 18 +``` + +Path resolution: `./file.ts` = same directory as policy; `../parent.ts` = parent; `./utils/helper.ts` = subdirectory. All normalized to `@local` internally. `@local/user/id` → `$PACKROOT/user/id.ts`. + +## Behavior & Constraints + +- TypeScript files must live inside the policy pack root. They can import other local modules and built-in `@sentrie/*` modules. +- Exported functions and types are available to policies. Relative imports in `.ts` files are resolved relative to that file and normalized to `@local`. +- Default alias is the last path segment (e.g. `utils` for `./utils.ts`). Use `as alias` to override. + +## Constraints & Edge Cases + +- Module must export the requested names. Invalid path or missing export causes load error. Use [Built-in TypeScript Modules](/reference/typescript_modules/) for common operations; add custom modules for pack-specific logic. diff --git a/src/content/docs/getting-started/introduction.md b/src/content/docs/getting-started/introduction.md new file mode 100644 index 0000000..5ce6aae --- /dev/null +++ b/src/content/docs/getting-started/introduction.md @@ -0,0 +1,56 @@ +--- +title: Introduction & Core Philosophy +description: Sentrie is a policy engine with deterministic evaluation. This page states what Sentrie is and why determinism is central. +--- + +# Introduction & Core Philosophy + +Sentrie is an open-source policy engine. You write business rules in a dedicated language; the engine evaluates them with deterministic, bounded execution. Use it when you need consistent policy decisions across services, auditability, and predictable performance. + +## What Sentrie Is + +- **Policy engine**: Evaluates declarative rules against input facts; returns decisions (e.g. allow/deny, or values). +- **Single binary**: No external runtime; supports macOS (arm64, x64), Linux (x64, arm64), Windows (x64, arm64). +- **Structured language**: Namespaces, policies, rules, facts, shapes; optional TypeScript modules for extra logic. + +## Determinism + +Evaluation is **deterministic**: same inputs and policy pack produce the same outputs. No hidden state, no nondeterministic APIs in the core language. + +- **Bounded execution**: Language is non–Turing complete (no arbitrary loops). All evaluations terminate. +- **Predictable performance**: No infinite loops or stack overflow from policy code. +- **Auditability**: Decisions are traceable; computation is bounded and reproducible. +- **Safe execution**: Designed for production; policy code cannot escape the evaluation sandbox. + +## Syntax + +Program structure (conceptual): + +```text +namespace + +shape { ... } + +policy { + fact : as + rule = default when { yield } + export decision of +} +``` + +- One namespace per file; namespace must be the first statement. +- Policies contain facts (inputs), rules (logic), and exports (rules exposed for evaluation). + +## Behavior & Constraints + +- **Facts**: Required by default; use `?` for optional. All facts are non-nullable. Optional facts may have a default. +- **Rules**: Must yield a value in the body when the `when` condition is true; otherwise the default (or `unknown` if no default) is used. +- **Exports**: A policy must export at least one rule for it to be evaluable via CLI or API. +- **TypeScript modules**: Allowed for extra functions; execution remains bounded by the engine. + +## Constraints & Edge Cases + +- Policy logic cannot cause infinite loops or unbounded recursion in the Sentrie language. +- Required facts must be provided at evaluation time or evaluation fails. +- Constraint validation failures abort evaluation immediately. +- TypeScript modules are sandboxed; they cannot break determinism of the policy language itself. diff --git a/src/content/docs/getting-started/quick-start.md b/src/content/docs/getting-started/quick-start.md new file mode 100644 index 0000000..b7931db --- /dev/null +++ b/src/content/docs/getting-started/quick-start.md @@ -0,0 +1,108 @@ +--- +title: Quick Start +description: Install Sentrie and run your first policy evaluation. +--- + +# Quick Start + +Install the Sentrie binary and evaluate a policy from the command line. This page covers installation and a minimal run (no policy pack layout). + +## Syntax + +Install (macOS with Homebrew): + +```bash +brew install sentrie-sh/tap/sentrie +``` + +Install (macOS, Linux, WSL2 — install script): + +```bash +curl -fsSL https://sentrie.sh/install.sh | bash +``` + +Install (Windows): + +```bash +irm https://sentrie.sh/install.ps1 | iex +``` + +Specific version (script): append version to the install script (e.g. `bash -s v0.1.0` for Unix; set `$v="0.1.0"` for Windows). + +Verify: + +```bash +sentrie --version +``` + +## Parameters + +| Step | Required | Description | +| :--- | :--- | :--- | +| Policy pack path | Yes | Directory containing `*.sentrie` and optional pack manifest. | +| Target | Yes | `namespace/policy` or `namespace/policy/rule`. | +| Facts | Depends | JSON object of fact names to values; required if the policy declares required facts. | + +**Returns:** Exit code 0 on success; non-zero on evaluation or CLI error. Decision output is printed to stdout. + +## Examples + +### Basic Usage + +Create a minimal pack and run one rule: + +```bash +mkdir my-pack && cd my-pack +sentrie init my-pack +``` + +Add a policy file (e.g. `policy.sentrie`): + +```sentrie +namespace com/example/app + +shape User { + role!: string + status!: string +} + +policy access { + fact user: User as u + + rule allow = default false { + yield u.role == "admin" or (u.role == "user" and u.status == "active") + } + + export decision of allow +} +``` + +Evaluate (from the pack directory): + +```bash +sentrie exec com/example/app/access/allow --facts '{"user": {"role": "user", "status": "active"}}' +``` + +### Advanced Usage + +Evaluate all exported rules in a policy and pass multiple facts: + +```bash +sentrie exec com/example/app/access --facts '{"user": {"role": "admin", "status": "active"}}' +``` + +Facts JSON keys must match the fact names (or aliases) declared in the policy. Required facts must be present. + +## Behavior & Constraints + +- **Single binary**: No extra runtime; the executable is self-contained. +- **Platforms**: macOS (arm64, x64), Linux (x64, arm64), Windows (x64, arm64). +- **Facts**: Required facts must be provided; otherwise evaluation fails. Optional facts may be omitted if they have defaults. +- **Target**: Use `namespace/policy` to evaluate all exported rules; use `namespace/policy/rule` for a single rule. + +## Constraints & Edge Cases + +- Missing required fact → evaluation error. +- Invalid JSON in `--facts` → CLI error. +- Invalid target path (unknown namespace/policy/rule) → error. +- At least one rule must be exported from a policy to be executable via `sentrie exec`. diff --git a/src/content/docs/index.mdx b/src/content/docs/index.mdx index 27e3f2f..28f5063 100644 --- a/src/content/docs/index.mdx +++ b/src/content/docs/index.mdx @@ -11,10 +11,10 @@ hero: file: ../../assets/freeze.svg actions: - text: Quick Start - link: /getting-started/installation + link: /getting-started/quick-start icon: right-arrow - text: Documentation - link: getting-started/what-is-sentrie + link: getting-started/introduction icon: external variant: minimal --- @@ -24,41 +24,39 @@ import { Card, CardGrid } from "@astrojs/starlight/components"; ## Features - - Powerful expressions with arithmetic, logical, comparison, and collection - operations. Support for quantifiers, pattern matching, and conditional - expressions. + + Install Sentrie and run your first policy evaluation. Installation and + basic usage.
- Learn more + Learn more
- - Build complex policies from simpler policies by exporting decisions that can - be used by other policies. + + Comprehensive type system with shapes, constraints, and built-in + validations.
- Learn more + Learn more
- - Seamlessly integrate TypeScript modules for complex logic. Built-in modules - for data manipulation, cryptography, and more. + + Arithmetic, logical, comparison, and collection operations. Quantifiers, + pattern matching, and conditionals.
- Learn more + Learn more
- - Run Sentrie as a service to evaluate policies using a well-defined and - minimal HTTP API. + + Build complex policies from simpler policies by exporting and importing + decisions.
- Learn more + Learn more
- - The Sentrie CLI provides a powerful set of tools for managing your policies - and JavaScript modules. + + Built-in TypeScript modules and writing custom modules for complex logic.
- Learn more + Learn more
- - Comprehensive type system with shapes, constraints, and built-in - validations. + + Run Sentrie as a service with a well-defined HTTP API for policy + evaluation.
- Learn more + Learn more
diff --git a/src/content/docs/language-concepts/pattern-matching-conditionals.md b/src/content/docs/language-concepts/pattern-matching-conditionals.md new file mode 100644 index 0000000..ec65b24 --- /dev/null +++ b/src/content/docs/language-concepts/pattern-matching-conditionals.md @@ -0,0 +1,67 @@ +--- +title: Pattern Matching & Conditionals +description: How conditional selection (ternary, Elvis), pattern matching (matches), and state checks work in Sentrie. +--- + +# Pattern Matching & Conditionals + +Sentrie uses trinary logic (`true`, `false`, `unknown`) and provides the ternary and Elvis operators for conditional values, plus `matches` for regex and `is defined` / `is empty` for state checks. This page describes how these work. + +## Syntax + +**Ternary:** `condition ? trueValue : falseValue` + +**Elvis:** `expression ?: defaultValue` (equivalent to `expression ? expression : defaultValue`) + +**Pattern match:** `string matches pattern` + +**Definedness:** `value is defined` | `value is not defined` + +**Emptiness:** `value is empty` | `value is not empty` + +## Parameters + +| Construct | Left | Right | Description | +| :--- | :--- | :--- | :--- | +| `? :` | condition (trinary) | trueValue, falseValue | If condition is truthy, result is trueValue; else falseValue. | +| `?:` | expression | defaultValue | If expression is truthy, result is expression; else defaultValue. | +| `matches` | string | string (regex) | True if string matches the regex pattern. | +| `is defined` | any | — | True if value is not undefined. | +| `is empty` | string/list/map | — | True if empty (e.g. `""`, `[]`, `{}`). | + +**Returns:** For ternary/Elvis: the selected value (any type). For `matches`, `is defined`, `is empty`: boolean (or trinary where applicable). Non-truthy for Elvis includes `false`, `null`, `undefined` (treated as unknown), `0`, `""`, empty collections. + +## Examples + +### Basic Usage + +```sentrie +let status: string = age >= 18 ? "adult" : "minor" +let displayName: string = user.name ?: "Anonymous" +let valid: bool = email matches "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" +let hasPhone: bool = user.phone is defined +``` + +### Advanced Usage + +```sentrie +let final_price: number = product.in_stock + ? (product.category == "Electronics" ? product.price * 0.9 : product.price) + : 0.0 + +let safeItems: list[string] = items ?: [] +``` + +## Behavior & Constraints + +- **Ternary:** Condition is evaluated first; only the chosen branch is evaluated. Nested ternaries are allowed; precedence is right-associative. +- **Elvis:** Shorthand for “use this value or default.” `null`/`undefined` are non-truthy (unknown in trinary), so the default is used. +- **matches:** Right-hand side is a string regex. Patterns are compiled and cached. Match is against the whole string unless the pattern allows partial match. +- **is defined:** Use for optional shape fields or values that may be undefined. Accessing undefined without checking can propagate unknown. +- **is empty:** Applies to strings, lists, and maps. Non-empty means at least one character or element. + +## Constraints & Edge Cases + +- Unknown (trinary) in a condition is not truthy; the false branch of ternary is used, and Elvis returns the default. +- Regex syntax is that of the engine (e.g. escaping in strings). Invalid regex causes error. +- `in` / `contains` for collections are documented in the reference; use with conditionals as needed. diff --git a/src/content/docs/language-concepts/policy-composition.md b/src/content/docs/language-concepts/policy-composition.md new file mode 100644 index 0000000..86a74de --- /dev/null +++ b/src/content/docs/language-concepts/policy-composition.md @@ -0,0 +1,123 @@ +--- +title: Policy Composition +description: "How exporting and importing rules work: syntax, attachments, sandboxing, and namespace resolution." +--- + +# Policy Composition + +Policies export rules so they can be executed via CLI/API or imported by other policies. Importing a rule runs it in isolation with only the facts you supply via `with` clauses. This page describes how export and import work. + +## Syntax + +**Export:** +```text +export decision of ruleName +export decision of ruleName + attach name as expression + attach name2 as expression2 +``` + +**Import:** +```text +rule localName = import decision of ruleName + from namespace/policy + with targetFact as expression + with targetFact2 as expression2 +``` + +## Parameters + +| Element | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| `ruleName` | identifier | Yes | Rule being exported or imported; must be exported in the target policy. | +| `namespace/policy` | path | Yes | Fully qualified namespace and policy (e.g. `com/example/auth/userAccess`). Same-namespace: policy name only. | +| `with fact as expr` | clause | Yes for each required fact | Maps current policy expression to target policy fact name (alias). | +| `attach name as expr` | clause | No | Evaluated when rule is executed; attached to decision for importers. | + +**Returns:** Exported rule’s decision (trinary or value). If attachments exist, import returns an object with the decision and attachment fields (e.g. `authResult.role`). + +## Examples + +### Basic Usage + +```sentrie +namespace com/example/auth + +policy userAccess { + fact user: User as currentUser + + rule isAdmin = default false when user.role is defined { + yield user.role == "admin" or user.role == "super_admin" + } + + export decision of isAdmin +} +``` + +```sentrie +namespace com/example/resources + +policy documentAccess { + fact user: User as currentUser + fact document: Document as currentDocument + + rule hasAdminAccess = import decision of isAdmin + from com/example/auth/userAccess + with currentUser as user + + rule canRead = default false { + yield hasAdminAccess or document.owner == user.id + } + + export decision of canRead +} +``` + +### Advanced Usage + +Export with attachments; import and use them: + +```sentrie +export decision of isAdmin + attach role as user.role + attach permissions as user.permissions +``` + +```sentrie +rule authResult = import decision of isAdmin + from com/example/auth/userAccess + with currentUser as user + +rule canRead = default false { + let userRole = authResult.role + let userPerms = authResult.permissions + yield authResult and userRole == "admin" +} +``` + +Same-namespace import (policy name only): + +```sentrie +namespace com/example/auth + +policy resources { + rule admin = import decision of isAdmin + from userAccess + with u as user +} +``` + +## Behavior & Constraints + +- **Export:** A policy must export at least one rule. Only exported rules are executable via CLI/API and importable. Rules in the same policy can reference each other without export. +- **Import:** The imported rule runs in a sandbox: it sees only the facts provided by `with` clauses. It cannot access the calling policy’s other facts or context. Facts are type-checked against the target policy. +- **Attachments:** Evaluated at rule execution; any expression allowed. Import side accesses via dot notation on the imported rule’s result. +- **Recursion:** Circular imports (A imports B, B imports A) are prevented and will fail. +- **Namespace:** Use full `namespace/policy` or, in the same namespace, policy name only. Fact names in `with` must match the target policy’s fact alias. + +## Constraints & Edge Cases + +- Rule not found: ensure the rule is exported and the namespace/policy path and rule name are correct (case-sensitive). +- Fact not found: use the target policy’s fact alias in `with`, not the original name. +- Type mismatch: the expression in `with` must evaluate to the type expected by the target policy’s fact. +- Missing attachments: check with `is defined` if an attachment may be absent before accessing it. diff --git a/src/content/docs/language-concepts/type-system-shapes.md b/src/content/docs/language-concepts/type-system-shapes.md new file mode 100644 index 0000000..e96bf95 --- /dev/null +++ b/src/content/docs/language-concepts/type-system-shapes.md @@ -0,0 +1,78 @@ +--- +title: Type System & Shapes Overview +description: "How types, shapes, and constraints work in Sentrie: structure, validation, and composition." +--- + +# Type System & Shapes Overview + +The type system defines values (primitives, collections, documents) and structured data (shapes). Constraints validate values at runtime. Understanding how types and shapes work is required to define facts and rules correctly. + +## Syntax + +**Primitives:** `number` | `string` | `trinary` | `bool` | `document` + +**Collections:** `list[T]` | `map[T]` | `record[T1, T2, ...]` + +**Shape (data model):** +```text +shape Name { + field!: type + field?: type + field: type +} +``` + +**Shape (alias):** `shape Name baseType @constraint1 @constraint2` + +**Composition:** `shape Child with Base { ... }` + +**Constraints:** Applied with `@` on types (e.g. `number @min(0) @max(100)`). + +## Parameters + +| Concept | Required | Description | +| :--- | :--- | :--- | +| Field `!` | No | Required non-null; field must be present and not null. | +| Field `?` | No | Optional; field may be omitted. | +| No marker | — | Required but may be null. | +| `with Base` | No | Shape inherits all fields of Base plus its own. | + +**Returns:** N/A (type system). Constraint validation fails at runtime if a value does not meet the type or constraints; evaluation aborts. + +## Examples + +### Basic Usage + +```sentrie +shape User { + name!: string + age: number + email?: string +} + +let u: User = { name: "Alice", age: 28 } +``` + +### Advanced Usage + +```sentrie +shape Base { id!: string } +shape Extended with Base { role!: string } + +let e: Extended = { id: "1", role: "admin" } +``` + +## Behavior & Constraints + +- **Primitives:** `number` (float64), `string`, `trinary` (`true`/`false`/`unknown`), `bool` (subset of trinary), `document` (JSON-like). +- **Collections:** `list[T]` index by number; `map[T]` keys are strings (dot or `["key"]` access); `record[T1,T2,...]` fixed-length tuple. +- **Shapes:** Define contracts for facts and `let` bindings. Optional fields may be omitted; use `is defined` to check. +- **Constraints:** Validate at runtime. Failing constraint validation aborts evaluation immediately. +- **Cast:** `cast expr as Type` converts and validates against the target type (and its constraints). + +## Constraints & Edge Cases + +- Map keys must be strings. +- Type annotation on `let` is optional; if omitted, values are not validated against types. +- Circular shape composition is not allowed. +- Exported shapes are visible across namespaces; unexported shapes are namespace-local. diff --git a/src/content/docs/reference/arithmetic-operations.md b/src/content/docs/reference/arithmetic-operations.md index b0b4180..915bee1 100644 --- a/src/content/docs/reference/arithmetic-operations.md +++ b/src/content/docs/reference/arithmetic-operations.md @@ -1,138 +1,60 @@ --- title: Arithmetic Operations -description: Arithmetic operations provide mathematical calculations on numeric values in Sentrie. +description: "Arithmetic operators: +, -, *, /, %; types and edge cases." --- -Sentrie supports standard arithmetic operations for working with numeric values. All numeric operations work with the unified `number` type, which is backed by float64. +# Arithmetic Operations -## Basic Operations +Arithmetic operators operate on `number` (float64). All numeric operands are unified as `number`. Result type is `number`. -### Addition (`+`) +## Syntax -```sentrie -let sum: number = 5 + 3 -- Result: 8 -let total: number = 2.5 + 1.5 -- Result: 4.0 -``` - -### Subtraction (`-`) - -```sentrie -let difference: number = 10 - 7 -- Result: 3 -let result: number = 5.5 - 2.5 -- Result: 3.0 -``` - -### Multiplication (`*`) - -```sentrie -let product: number = 4 * 6 -- Result: 24 -let area: number = 3.75 * 2.0 -- Result: 7.5 +```text +expr + expr +expr - expr +expr * expr +expr / expr +expr % expr ``` -### Division (`/`) +Unary: `+ expr` | `- expr` -```sentrie -let quotient: number = 15 / 3 -- Result: 5.0 -let precise: number = 7 / 2 -- Result: 3.5 -``` +## Parameters -### Modulo (`%`) +| Operator | Description | Result | +| :--- | :--- | :--- | +| `+` | Addition | number | +| `-` | Subtraction | number | +| `*` | Multiplication | number | +| `/` | Division | number | +| `%` | Modulo (remainder) | number | -```sentrie -let remainder: number = 10 % 3 -- Result: 1 -let even_check: number = 8 % 2 -- Result: 0 -``` - -## Division and Precision - -### Integer Division - -```sentrie -let result: number = 8 / 3 -- Result: 2.6666666666666665 -let whole: number = 10 / 2 -- Result: 5.0 -``` - -Division always returns a numeric result. - -### Zero Division - -```sentrie --- This will cause an error -let invalid: number = 5 / 0 -- Error: division by zero -``` +**Returns:** `number`. Division by zero aborts evaluation. -## Mixed Mode Arithmetic +## Examples -### Mixed Numeric Operations +### Basic Usage ```sentrie -let mixed_add: number = 5 + 2.5 -- Result: 7.5 -let mixed_multiply: number = 3 * 1.5 -- Result: 4.5 -let result: number = 10 - 7.2 -- Result: 2.8 +let sum: number = 5 + 3 +let diff: number = 10 - 7 +let prod: number = 4 * 6 +let quot: number = 15 / 3 +let rem: number = 10 % 3 ``` -All numeric values are handled uniformly as the `number` type. - -## Practical Examples - -### Shape Calculations +### Advanced Usage ```sentrie -shape Rectangle { - width!: number - height!: number -} - -fact rect: Rectangle - let area: number = rect.width * rect.height -let perimeter: number = 2 * (rect.width + rect.height) -let aspect_ratio: number = rect.width / rect.height -``` - -### Percentages and Discounts - -```sentrie -let price: number = 100.0 -let discount_percent: number = 15 -let discount_amount: number = price * (discount_percent / 100.0) -let final_price: number = price - discount_amount --- Result: 85.0 +let safe: number = divisor != 0 ? 10 / divisor : 0.0 ``` -### Statistical Operations - -```sentrie -let scores: list[number] = [85, 92, 78, 96] -let total: number = reduce scores from 0 as score, idx { yield score } -let average: number = total / count scores --- Result: 87.75 -``` +## Behavior & Constraints -## Best Practices +- All operands are `number`; mixed integer/float is allowed. Division is float (e.g. 7/2 = 3.5). +- Modulo: remainder after division; divisor zero aborts. -### Use Explicit Types +## Constraints & Edge Cases -```sentrie --- Good: Clear type intention -let precise_result: number = 7 / 3 - --- Avoid: Type inference confusion -let result = 7 / 3 -``` - -### Handle Division by Zero - -```sentrie -let divisor: number = 0 -let safe_result: number = divisor != 0 ? 10 / divisor : 0.0 -``` - -### Use Parentheses for Clarity - -```sentrie --- Good: Clear precedence -let result: number = (2 + 3) * 4 - --- Avoid: Relying on operator precedence -let result: number = 2 + 3 * 4 -``` +- Division by zero aborts evaluation. Use a guard (e.g. ternary) to avoid. diff --git a/src/content/docs/reference/boolean-operations.md b/src/content/docs/reference/boolean-operations.md index ea25a39..a4b8d06 100644 --- a/src/content/docs/reference/boolean-operations.md +++ b/src/content/docs/reference/boolean-operations.md @@ -1,634 +1,58 @@ --- title: Boolean Operations -description: Boolean operations provide powerful ways to evaluate conditions, compare values, and make decisions in Sentrie. +description: Logical (and, or, xor, not) and comparison (==, !=, <, <=, >, >=) operators. --- -Sentrie provides a comprehensive set of boolean operations that allow you to evaluate conditions, compare values, and make logical decisions in your policies. These operations are essential for creating conditional logic and data validation. +# Boolean Operations -## Overview +Logical and comparison operators produce trinary or bool results. Operands are trinary; comparisons and logical ops follow [trinary](/reference/trinary) semantics. -Boolean operations in Sentrie include: +## Syntax -- **Trinary Logic**: Three-valued logic with `true`, `false`, and `unknown` -- **Conditional Operators**: Ternary (`? :`) and Elvis (`?:`) operators for conditional value selection -- **Logical Operations**: Boolean logic with `and`, `or`, `not` -- **Comparison Operations**: Equality, inequality, and ordering comparisons -- **Pattern Matching**: Regular expression matching -- **Collection Operations**: Testing collections with `any`, `all`, `in`, `contains` -- **State Checking**: Checking emptiness and definedness +**Logical:** `and` | `or` | `xor` | `not` | `!` -## Conditional Operators +**Comparison:** `==` | `!=` | `is` | `is not` | `<` | `<=` | `>` | `>=` -Sentrie provides two operators for conditional value selection: the ternary operator and the Elvis operator. The Elvis operator is a shorthand for a common pattern using the ternary operator. +**Conditional:** `condition ? trueValue : falseValue` | `expr ?: default` -### Ternary Operator (`? :`) +## Parameters -The ternary operator allows you to conditionally select between two values based on a boolean condition. +| Operator | Description | Returns | +| :--- | :--- | :--- | +| `and` | Logical AND (Kleene) | trinary | +| `or` | Logical OR (Kleene) | trinary | +| `xor` | Logical XOR | trinary | +| `not`, `!` | Logical NOT | trinary | +| `==`, `is` | Equality | trinary | +| `!=`, `is not` | Inequality | trinary | +| `<`, `<=`, `>`, `>=` | Ordering | trinary | +| `? :` | Ternary | type of chosen branch | +| `?:` | Elvis (default if not truthy) | type | -#### Syntax +**Returns:** Trinary or the selected value. Non-truthy for Elvis: false, unknown, null, 0, "", empty collection. -```sentrie -condition ? trueValue : falseValue -``` - -#### Examples - -```sentrie -let age: number = 25 -let status: string = age >= 18 ? "adult" : "minor" --- Result: "adult" - -let score: number = 85 -let grade: string = score >= 90 ? "A" : score >= 80 ? "B" : score >= 70 ? "C" : "F" --- Result: "B" - -let user_role: string = "admin" -let can_access: bool = user_role == "admin" ? true : false --- Result: true -``` - -#### Complex Ternary Logic - -```sentrie -shape Product { - name!: string - price!: number - category!: string - in_stock: bool -} - -fact product: Product - --- Complex pricing logic -let final_price: number = product.in_stock ? - (product.category == "Electronics" ? product.price * 0.9 : product.price) : - 0.0 --- Result: 899.991 (10% discount for electronics) - --- Status message -let status_message: string = product.in_stock ? - "Available for $" + product.price.toString() : - "Out of stock" --- Result: "Available for $999.99" -``` - -### Elvis Operator (`?:`) - -The Elvis operator (`?:`) provides a shorthand way to provide a default value when the left-hand expression is not truthy. It is equivalent to using the ternary operator with the same expression on both sides of the `?`. - -When the left-hand expression evaluates to a non-truthy value (including `false`, `null`, `undefined` → `unknown`, `0`, `""`, or empty collections), the default value is used. - -#### Syntax - -```sentrie -expression ?: defaultValue -``` - -This is equivalent to: - -```sentrie -expression ? expression : defaultValue -``` - -#### Examples - -```sentrie --- If product.price is not truthy (null/undefined → unknown, 0, etc.), use 100 as the default -let price: number = product.price ?: 100 - --- If user.name is empty or undefined (→ unknown), use "Anonymous" as the default -let displayName: string = user.name ?: "Anonymous" - --- If items list is empty or undefined (→ unknown), use an empty list as the default -let safeItems: list[string] = items ?: [] -``` - -:::note[Note] -When `null` or `undefined` values are used with the Elvis operator, they are converted to `unknown` in trinary logic. Since `unknown` is not truthy, the default value is used. -::: - -## Logical Operations - -For details on trinary logic, see the [Kleene's three-valued logic](/reference/trinary#kleene-truth-tables) section. - -## Comparison Operations - -### Equality (`==` and `is`) - -Both `==` and `is` operators check if two values are equal. - -#### Syntax - -```sentrie -value1 == value2 -value1 is value2 -``` - -#### Basic Examples - -```sentrie -let age: number = 25 -let is_adult: bool = age == 18 --- Result: false - -let name: string = "Alice" -let is_alice: bool = name is "Alice" --- Result: true - -let score1: number = 85.5 -let score2: number = 85.5 -let scores_equal = score1 == score2 --- Result: true -``` - -### Inequality (`!=` and `is not`) - -Both `!=` and `is not` operators check if two values are not equal. - -#### Syntax - -```sentrie -value1 != value2 -value1 is not value2 -``` - -#### Examples - -```sentrie -let age: number = 25 -let is_minor: bool = age != 18 --- Result: true - -let status: string = "active" -let is_inactive = status is not "inactive" --- Result: true - -let score: number = 85.5 -let is_perfect = score != 100.0 --- Result: true -``` - -### Greater Than (`>`) - -The `>` operator checks if the left operand is greater than the right operand. - -#### Syntax - -```sentrie -value1 > value2 -``` - -#### Examples - -```sentrie -let age: number = 25 -let is_adult: bool = age > 17 --- Result: true - -let score: number = 85.5 -let is_passing: bool = score > 80.0 --- Result: true - -let price: number = 99.99 -let is_expensive: bool = price > 50.0 --- Result: true -``` - -### Greater Than or Equal (`>=`) - -The `>=` operator checks if the left operand is greater than or equal to the right operand. - -#### Syntax - -```sentrie -value1 >= value2 -``` - -#### Examples - -```sentrie -let age: number = 18 -let can_vote: bool = age >= 18 --- Result: true - -let score: number = 80.0 -let is_passing: bool = score >= 80.0 --- Result: true - -let temperature: number = 32.0 -let is_freezing: bool = temperature >= 32.0 --- Result: true -``` - -### Less Than (`<`) - -The `<` operator checks if the left operand is less than the right operand. - -#### Syntax - -```sentrie -value1 < value2 -``` - -#### Examples - -```sentrie -let age: number = 16 -let is_minor: bool = age < 18 --- Result: true - -let score: number = 75.0 -let is_failing: bool = score < 80.0 --- Result: true - -let price: number = 25.0 -let is_cheap: bool = price < 50.0 --- Result: true -``` - -### Less Than or Equal (`<=`) - -The `<=` operator checks if the left operand is less than or equal to the right operand. - -#### Syntax - -```sentrie -value1 <= value2 -``` - -#### Examples - -```sentrie -let age: number = 18 -let is_minor: bool = age <= 17 --- Result: false - -let score: number = 80.0 -let is_passing: bool = score <= 100.0 --- Result: true - -let quantity: number = 10 -let is_limited: bool = quantity <= 10 --- Result: true -``` - -## Pattern Matching Operations +## Examples -### Regular Expression Matching (`matches`) - -The `matches` operator checks if a string matches a regular expression pattern. - -#### Syntax - -```sentrie -string matches pattern -``` - -#### Examples - -```sentrie -let email: string = "user@example.com" -let is_valid_email: bool = email matches "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" --- Result: true - -let phone: string = "+1234567890" -let is_valid_phone: bool = phone matches "^\\+?[1-9]\\d{1,14}$" --- Result: true - -let username: string = "alice123" -let is_valid_username: bool = username matches "^[a-zA-Z0-9_]{3,20}$" --- Result: true -``` - -## Collection Operations - -### Membership (`in` and `contains`) - -Both `in` and `contains` operators check if a value exists in a collection. - -`contains` checks if the left hand collection contains the right hand value. `in` checks if the left hand value is in the right hand collection. - -For purposes of clarity, we will use the following terminology: - -- `haystack` is the collection that is being searched -- `needle` is the value that is being searched for - -#### Syntax - -```sentrie -value in collection -collection contains value -``` - -#### Examples +### Basic Usage ```sentrie -let numbers: list[number] = [1, 2, 3, 4, 5] -let has_three: bool = 3 in numbers --- Result: true - -let colors: list[string] = ["red", "blue", "green"] -let has_red: bool = "red" in colors --- Result: true - -let permissions: list[string] = ["read", "write", "delete"] -let can_read: bool = "read" in permissions --- Result: true +let a: bool = true and false +let b: bool = age >= 18 +let c: string = age >= 18 ? "adult" : "minor" +let d: string = user.name ?: "Anonymous" ``` -#### Working with Maps - -For maps, if the `needle` is a string, it will be used as the key to check if the key exists in the `haystack`. if the `needle` is another map, then it will be used to check if the `needle` map is a subset of the `haystack` map. +### Advanced Usage ```sentrie -let user_permissions: map[string] = map[string]{ - "read": true, - "write": false, - "delete": true, - "admin": true -} - --- Check if the "read" permission is set -let has_read: bool = "read" in user_permissions and user_permissions["read"] == true --- Result: true - +let e: bool = user.role == "admin" or (user.role == "user" and user.status == "active") ``` -#### Negating `in` and `contains` +## Behavior & Constraints -`in` and `contains` can be negated by prefixing the operator with `not`, such as `not contains` and `not in`. This is equivalent to wrapping the expression in a unary `not` but results in a more readable form. +- Short-circuit: `and`/`or` evaluate left-to-right; right side may be skipped. Ternary evaluates only the chosen branch. +- Comparison: both sides must be comparable; result is trinary. -#### Syntax +## Constraints & Edge Cases -```sentrie -value not in collection -collection not contains value -``` - -## State Checking Operations - -### Emptiness Checking (`is empty` and `is not empty`) - -These operations check if a value is empty or not empty. - -#### Syntax - -```sentrie -value is empty -value is not empty -``` - -#### Basic Examples - -```sentrie -let empty_string: string = "" -let is_empty: bool = empty_string is empty --- Result: true - -let non_empty_string: string = "hello" -let is_not_empty: bool = non_empty_string is not empty --- Result: true - -let empty_list: list[number] = [] -let list_is_empty: bool = empty_list is empty --- Result: true - -let non_empty_list: list[number] = [1, 2, 3] -let list_is_not_empty: bool = non_empty_list is not empty --- Result: true -``` - -#### Working with Shapes - -```sentrie -shape User { - name!: string - email?: string - phone?: string -} - -let user: User = { - name: "Alice", - email: "alice@example.com" -} - --- Check if email is not empty -let has_email: bool = user.email is defined and user.email is not empty --- Result: true - --- Check if phone is empty -let phone_empty: bool = user.phone is defined ? user.phone is empty : true --- Result: true -``` - -### Definedness Checking (`is defined` and `is not defined`) - -These operations check if a value is defined or not defined. - -#### Syntax - -```sentrie -value is defined -value is not defined -``` - -#### Basic Examples - -```sentrie -shape User { - name!: string - email?: string - phone?: string -} - -let user: User = { - name: "Alice", - email: "alice@example.com" -} - --- Check if email is defined -let email_defined: bool = user.email is defined --- Result: true - --- Check if phone is not defined -let phone_not_defined: bool = user.phone is not defined --- Result: true - --- Safe access pattern -let display_email: string = user.email is defined ? user.email : "No email provided" --- Result: "alice@example.com" -``` - -#### Complex Definedness Logic - -```sentrie -shape Order { - id!: string - customer_name!: string - customer_email?: string - customer_phone?: string - shipping_address?: string -} - -let order: Order = { - id: "12345", - customer_name: "Alice", - customer_email: "alice@example.com" -} - --- Check if customer has contact information -let has_contact: bool = order.customer_email is defined or order.customer_phone is defined --- Result: true - --- Check if order is complete -let is_complete: bool = order.customer_name is not empty and - order.shipping_address is defined --- Result: false - --- Get contact method -let contact_method: string = order.customer_email is defined ? - "Email: " + order.customer_email : - order.customer_phone is defined ? - "Phone: " + order.customer_phone : - "No contact information" --- Result: "Email: alice@example.com" -``` - -## Complex Boolean Logic Examples - -### Access Control System - -```sentrie -shape User { - id!: string - username!: string - role!: string - age: number - email?: string - active: bool - permissions: list[string] -} - -shape Resource { - id!: string - name!: string - required_role: string - min_age: number - required_permissions: list[string] -} - -let user: User = { - id: "123", - username: "alice", - role: "admin", - age: 25, - email: "alice@example.com", - active: true, - permissions: ["read", "write", "delete", "admin"] -} - -let resource: Resource = { - id: "456", - name: "Sensitive Data", - required_role: "admin", - min_age: 18, - required_permissions: ["read", "admin"] -} - --- Complex access control logic -let can_access: bool = user.active and - user.age >= resource.min_age and - (user.role == resource.required_role or - user.role == "superuser") and - all resource.required_permissions as perm, idx { - yield perm in user.permissions - } --- Result: true -``` - -### Data Validation System - -```sentrie -use {length} from @sentrie/js as str - -shape RegistrationData { - username!: string - email?: string - password!: string - age: number - terms_accepted: bool -} - -let registration: RegistrationData = { - username: "alice123", - email: "alice@example.com", - password: "SecurePass123", - age: 25, - terms_accepted: true -} - --- Comprehensive validation -let is_valid_registration: bool = - registration.username is not empty and - str.length(registration.username) >= 3 and - str.length(registration.username) <= 20 and - registration.username matches "^[a-zA-Z0-9_]+$" and - registration.password is not empty and - str.length(registration.password) >= 8 and - registration.password matches ".*[A-Z].*" and - registration.password matches ".*[0-9].*" and - registration.age >= 13 and - registration.age <= 120 and - registration.terms_accepted and - ( - registration.email is not defined or - registration.email matches "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" - ) --- Result: true -``` - -## Best Practices - -### Use Clear Variable Names - -```sentrie --- Good -let is_valid_user: bool = user.age >= 18 and user.email is defined - --- Avoid -let flag: bool = user.age >= 18 and user.email is defined -``` - -### Combine Operations Readably - -```sentrie --- Good: Clear grouping with parentheses -let can_access: bool = (user.active and user.verified) or - (user.role == "admin" and user.age >= 18) - --- Avoid: Ambiguous precedence -let can_access: bool = user.active and user.verified or user.role == "admin" and user.age >= 18 -``` - -### Handle Edge Cases - -```sentrie --- Check for empty values before operations -let is_valid: bool = user.name is not empty and - str.length(user.name) >= 2 and - user.email is defined and - user.email is not empty -``` - -### Use Appropriate Operators - -```sentrie --- Use 'is' for readability with null checks -let has_email: bool = user.email is defined - --- Use '==' for value comparisons -let is_admin: bool = user.role == "admin" - --- Use 'in' for collection membership -let can_read: bool = "read" in user.permissions -``` +- `unknown` in logical ops propagates per Kleene. Equality with `unknown` yields `unknown`. diff --git a/src/content/docs/reference/collection-operations.md b/src/content/docs/reference/collection-operations.md index b8f051a..a3e27b1 100644 --- a/src/content/docs/reference/collection-operations.md +++ b/src/content/docs/reference/collection-operations.md @@ -1,941 +1,67 @@ --- title: Collection Operations -description: Collection operations provide powerful ways to transform, filter, and analyze collections in Sentrie. +description: "Quantifiers and transformers: any, all, filter, map, reduce, count, distinct." --- -Sentrie provides a comprehensive set of collection operations that allow you to manipulate and analyze lists and maps declaratively. These operations are essential for working with collections of data in policies. +# Collection Operations -## Overview +Collection operations apply to lists and maps. They are declarative: they return new values or collections and do not mutate the input. Syntax uses a block with `yield`. -Collection operations in Sentrie are **only valid on collections** (lists and maps) and provide functional programming capabilities for data transformation. Each operation follows a consistent syntax pattern and returns new collections or values without modifying the original data. +## Syntax -## `any` Operation - -The `any` operation checks if at least one element in a collection satisfies a condition. - -### Syntax - -```sentrie -any collection as element, index { yield expression } -``` - -### Examples - -```sentrie -let numbers: list[number] = [1, 2, 3, 4, 5] -let has_even: bool = any numbers as num, idx { - yield num % 2 == 0 -} --- Result: true - -let scores: list[number] = [85, 92, 78, 96, 85] -let has_perfect_score: bool = any scores as score, idx { - yield score == 100 -} --- Result: false - -let words: list[string] = ["apple", "banana", "cherry"] -let has_long_word: bool = any words as word, idx { - yield word.length() > 5 -} --- Result: true -``` - -### Working with Shapes - -```sentrie -shape User { - name!: string - age: number - role!: string -} - -let users: list[User] = [ - { name: "Alice", age: 25, role: "admin" }, - { name: "Bob", age: 30, role: "user" }, - { name: "Charlie", age: 35, role: "moderator" } -] - --- Check if any user is admin -let has_admin: bool = any users as user, idx { - yield user.role == "admin" -} --- Result: true - --- Check if any user is underage -let has_minor: bool = any users as user, idx { - yield user.age < 18 -} --- Result: false - --- Check if any user has long name -let has_long_name: bool = any users as user, idx { - yield user.name.length() > 10 -} --- Result: false -``` - -## `all` Operation - -The `all` operation checks if all elements in a collection satisfy a condition. - -### Syntax - -```sentrie -all collection as element, index { yield expression } -``` - -### Examples - -```sentrie -let numbers: list[number] = [2, 4, 6, 8, 10] -let all_even: bool = all numbers as num, idx { - yield num % 2 == 0 -} --- Result: true - -let scores: list[number] = [85, 92, 78, 96, 85] -let all_passing: bool = all scores as score, idx { - yield score >= 80 -} --- Result: false - -let words: list[string] = ["apple", "banana", "cherry"] -let all_short: bool = all words as word, idx { - yield word.length() <= 6 -} --- Result: true -``` - -### Working with Shapes - -```sentrie -shape Product { - name!: string - price!: number - in_stock: bool -} - -let products: list[Product] = [ - { name: "Laptop", price: 999.99, in_stock: true }, - { name: "Mouse", price: 29.99, in_stock: true }, - { name: "Keyboard", price: 79.99, in_stock: true } -] - --- Check if all products are in stock -let all_in_stock: bool = all products as product, idx { - yield product.in_stock -} --- Result: true - --- Check if all products are expensive -let all_expensive: bool = all products as product, idx { - yield product.price > 50.0 -} --- Result: true - --- Check if all products have short names -let all_short_names: bool = all products as product, idx { - yield product.name.length() <= 10 -} --- Result: true -``` - -## `filter` Operation - -The `filter` operation creates a new collection containing only elements that satisfy a given condition. - -### Syntax - -```sentrie -filter collection as element, index block_expression -``` - -The `yield` statement returns a `bool` value. Only elements for which the predicate is truthy are included in the result. The `index` parameter is optional. - -### Basic Examples - -```sentrie -let numbers: list[number] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - --- Filter even numbers -let evens: list[number] = filter numbers as num, idx { - yield num % 2 == 0 -} --- Result: [2, 4, 6, 8, 10] - --- Filter numbers greater than 5 -let large_numbers: list[number] = filter numbers as num, idx { - yield num > 5 -} --- Result: [6, 7, 8, 9, 10] - --- Filter numbers at even indices -let even_indexed: list[number] = filter numbers as num, idx { - yield idx % 2 == 0 -} --- Result: [1, 3, 5, 7, 9] -``` - -### Complex Filtering Conditions - -```sentrie -let scores: list[number] = [85, 92, 78, 96, 85, 88, 91, 77, 94, 89] - --- Filter passing scores (>= 80) -let passing_scores: list[number] = filter scores as score, idx { - yield score >= 80 -} --- Result: [85, 92, 96, 85, 88, 91, 94, 89] - --- Filter scores in top 20% -let top_scores: list[number] = filter scores as score, idx { - yield score >= 90 -} --- Result: [92, 96, 91, 94] - --- Filter scores that are above average -let above_average: list[number] = filter scores as score, idx { - -- Calculate average (simplified for example) - yield score > 87 -} --- Result: [92, 96, 88, 91, 94, 89] -``` - -### Working with Shapes - -```sentrie -shape Employee { - name!: string - department!: string - salary!: number - years_experience: number -} - -let employees: list[Employee] = [ - { name: "Alice", department: "Engineering", salary: 95000.0, years_experience: 5 }, - { name: "Bob", department: "Marketing", salary: 75000.0, years_experience: 3 }, - { name: "Charlie", department: "Engineering", salary: 110000.0, years_experience: 8 }, - { name: "Diana", department: "Sales", salary: 65000.0, years_experience: 2 }, - { name: "Eve", department: "Engineering", salary: 85000.0, years_experience: 4 } -] - --- Filter by department -let engineers: list[Employee] = filter employees as emp, idx { - yield emp.department == "Engineering" -} --- Result: Alice, Charlie, Eve - --- Filter by salary -let high_earners: list[Employee] = filter employees as emp, idx { - yield emp.salary >= 90000.0 -} --- Result: Alice, Charlie - --- Filter by experience -let experienced: list[Employee] = filter employees as emp, idx { - yield emp.years_experience >= 5 -} --- Result: Alice, Charlie - --- Complex condition: Engineering with high salary -let senior_engineers: list[Employee] = filter employees as emp, idx { - yield emp.department == "Engineering" and emp.salary >= 90000.0 -} --- Result: Alice, Charlie -``` - -### String Filtering - -```sentrie -let words: list[string] = ["apple", "banana", "cherry", "date", "elderberry", "fig"] - --- Filter words starting with 'a' -let a_words: list[string] = filter words as word, idx { - yield word.starts_with("a") -} --- Result: ["apple"] - --- Filter words longer than 5 characters -let long_words: list[string] = filter words as word, idx { - yield word.length() > 5 -} --- Result: ["banana", "cherry", "elderberry"] - --- Filter words containing 'e' -let e_words: list[string] = filter words as word, idx { - yield word.has_substring("e") -} --- Result: ["apple", "cherry", "elderberry"] -``` - -## `first` Operation - -The `first` operation returns the first element in a collection that satisfies a given condition. Unlike `any` which returns a boolean, `first` returns the actual element value. - -### Syntax - -```sentrie -first collection as element, index block_expression -``` - -The `yield` statement returns a `bool` value. The operation returns the first element for which the predicate is truthy. If no element satisfies the condition, it returns `undefined`. The `index` parameter is optional. - -### Basic Examples - -```sentrie -let numbers: list[number] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - --- Find first even number -let first_even: number = first numbers as num, idx { - yield num % 2 == 0 -} --- Result: 2 - --- Find first number greater than 5 -let first_large: number = first numbers as num, idx { - yield num > 5 -} --- Result: 6 - --- Find first number at even index -let first_even_indexed: number = first numbers as num, idx { - yield idx % 2 == 0 -} --- Result: 1 (first element at index 0) - --- Find first negative number (none exist) -let first_negative: number = first numbers as num, idx { - yield num < 0 -} --- Result: undefined -``` - -### Working with Shapes - -```sentrie -shape Employee { - name!: string - department!: string - salary!: number - years_experience: number -} - -let employees: list[Employee] = [ - { name: "Alice", department: "Engineering", salary: 95000.0, years_experience: 5 }, - { name: "Bob", department: "Marketing", salary: 75000.0, years_experience: 3 }, - { name: "Charlie", department: "Engineering", salary: 110000.0, years_experience: 8 }, - { name: "Diana", department: "Sales", salary: 65000.0, years_experience: 2 }, - { name: "Eve", department: "Engineering", salary: 85000.0, years_experience: 4 } -] - --- Find first engineer -let first_engineer: Employee = first employees as emp, idx { - yield emp.department == "Engineering" -} --- Result: Alice - --- Find first high earner -let first_high_earner: Employee = first employees as emp, idx { - yield emp.salary >= 90000.0 -} --- Result: Alice - --- Find first experienced employee -let first_experienced: Employee = first employees as emp, idx { - yield emp.years_experience >= 5 -} --- Result: Alice - --- Find first sales person -let first_sales: Employee = first employees as emp, idx { - yield emp.department == "Sales" -} --- Result: Diana -``` - -### String Operations - -```sentrie -let words: list[string] = ["apple", "banana", "cherry", "date", "elderberry", "fig"] - --- Find first word starting with 'a' -let first_a_word: string = first words as word, idx { - yield word.starts_with("a") -} --- Result: "apple" - --- Find first word longer than 5 characters -let first_long_word: string = first words as word, idx { - yield word.length() > 5 -} --- Result: "banana" - --- Find first word containing 'e' -let first_e_word: string = first words as word, idx { - yield word.has_substring("e") -} --- Result: "apple" - --- Find first word starting with 'z' (none exist) -let first_z_word: string = first words as word, idx { - yield word.starts_with("z") -} --- Result: undefined -``` - -### Complex Conditions - -```sentrie -shape Product { - id!: string - name!: string - price!: number - category!: string - in_stock: bool -} - -let products: list[Product] = [ - { id: "1", name: "Laptop", price: 999.99, category: "Electronics", in_stock: true }, - { id: "2", name: "Mouse", price: 29.99, category: "Electronics", in_stock: false }, - { id: "3", name: "Book", price: 19.99, category: "Books", in_stock: true }, - { id: "4", name: "Keyboard", price: 79.99, category: "Electronics", in_stock: true }, - { id: "5", name: "Tablet", price: 599.99, category: "Electronics", in_stock: false } -] - --- Find first in-stock electronics under $100 -let first_affordable_electronics: Product = first products as product, idx { - yield product.category == "Electronics" and product.in_stock and product.price < 100.0 -} --- Result: Keyboard - --- Find first expensive product (over $500) -let first_expensive: Product = first products as product, idx { - yield product.price > 500.0 -} --- Result: Laptop - --- Find first out-of-stock item -let first_out_of_stock: Product = first products as product, idx { - yield not product.in_stock -} --- Result: Mouse - --- Find first book -let first_book: Product = first products as product, idx { - yield product.category == "Books" -} --- Result: Book -``` - -### Practical Use Cases - -```sentrie -shape User { - name!: string - email!: string - verified: bool - last_login: string -} - -let users: list[User] = [ - { name: "Alice", email: "alice@example.com", verified: true, last_login: "2024-01-15" }, - { name: "Bob", email: "bob@example.com", verified: false, last_login: "2024-01-10" }, - { name: "Charlie", email: "charlie@example.com", verified: true, last_login: "2024-01-20" }, - { name: "Diana", email: "diana@example.com", verified: false, last_login: "2024-01-05" } -] - --- Find first verified user -let first_verified: User = first users as user, idx { - yield user.verified -} --- Result: Alice - --- Find first user with recent login (after 2024-01-12) -let first_recent_user: User = first users as user, idx { - yield user.last_login > "2024-01-12" -} --- Result: Alice - --- Find first unverified user -let first_unverified: User = first users as user, idx { - yield not user.verified -} --- Result: Bob - --- Find first user with specific email domain -let first_gmail_user: User = first users as user, idx { - yield user.email.has_substring("@gmail.com") -} --- Result: undefined (no Gmail users) -``` - -### Comparison with Other Operations - -```sentrie -let numbers: list[number] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - --- any: returns boolean -let has_even: bool = any numbers as num, idx { - yield num % 2 == 0 -} --- Result: true - --- first: returns the actual value -let first_even: number = first numbers as num, idx { - yield num % 2 == 0 -} --- Result: 2 - --- filter: returns all matching elements -let all_even: list[number] = filter numbers as num, idx { - yield num % 2 == 0 -} --- Result: [2, 4, 6, 8, 10] - --- Using first with undefined check -let first_large: number = first numbers as num, idx { - yield num > 15 -} -let has_large: bool = first_large is defined --- Result: false (first_large is undefined) -``` - -## `map` Operation - -The `map` operation applies a function to each element of a collection, creating a new collection with the transformed values. - -### Syntax - -```sentrie -map collection as element, index block_expression -``` - -The `yield` statement returns the new element value for the resulting collection. The `index` parameter is optional and represents the position of the element in the collection. - -### Basic Examples - -```sentrie -let numbers: list[number] = [1, 2, 3, 4, 5] - --- Double each number -let doubled: list[number] = map numbers as num, idx { - yield num * 2 -} --- Result: [2, 4, 6, 8, 10] - --- Square each number -let squared: list[number] = map numbers as num, idx { - yield num * num -} --- Result: [1, 4, 9, 16, 25] - --- Convert numbers to strings -let strings: list[string] = map numbers as num, idx { - yield num.toString() -} --- Result: ["1", "2", "3", "4", "5"] -``` - -### Using Index Parameter - -```sentrie -let fruits: list[string] = ["apple", "banana", "cherry"] - --- Add index to each fruit name -let indexed_fruits: list[string] = map fruits as fruit, idx { - yield (idx + 1).toString() + ". " + fruit -} --- Result: ["1. apple", "2. banana", "3. cherry"] - --- Create pairs of index and value -let pairs: list[string] = map fruits as fruit, idx { - yield "(" + idx.toString() + ", " + fruit + ")" -} --- Result: ["(0, apple)", "(1, banana)", "(2, cherry)"] -``` - -### Working with Shapes - -```sentrie -shape User { - name!: string - age: number - email?: string -} - -let users: list[User] = [ - { name: "Alice", age: 25, email: "alice@example.com" }, - { name: "Bob", age: 30 }, - { name: "Charlie", age: 35, email: "charlie@example.com" } -] - --- Extract names -let names: list[string] = map users as user, idx { - yield user.name -} --- Result: ["Alice", "Bob", "Charlie"] - --- Create user summaries -let summaries: list[string] = map users as user, idx { - let email_text: string = user.email is defined ? " (" + user.email + ")" : " (no email)" - yield user.name + ", age " + user.age.toString() + email_text -} --- Result: ["Alice, age 25 (alice@example.com)", "Bob, age 30 (no email)", "Charlie, age 35 (charlie@example.com)"] - --- Calculate birth years (assuming current year is 2024) -let birth_years: list[number] = map users as user, idx { - yield 2024 - user.age -} --- Result: [1999, 1994, 1989] -``` - -### Complex Transformations - -```sentrie -shape Product { - id!: string - name!: string - price!: number - category!: string -} - -let products: list[Product] = [ - { id: "1", name: "Laptop", price: 999.99, category: "Electronics" }, - { id: "2", name: "Book", price: 19.99, category: "Books" }, - { id: "3", name: "Mouse", price: 29.99, category: "Electronics" } -] - --- Apply discount and format prices -let discounted_products: list[string] = map products as product, idx { - let discounted_price: number = product.price * 0.9 -- 10% discount - yield product.name + " - $" + discounted_price.toString() + " (was $" + product.price.toString() + ")" -} --- Result: ["Laptop - $899.991 (was $999.99)", "Book - $17.991 (was $19.99)", "Mouse - $26.991 (was $29.99)"] - --- Create product codes -let product_codes: list[string] = map products as product, idx { - let category_code: string = product.category == "Electronics" ? "E" : "B" - yield category_code + (idx + 1).toString() + "-" + product.id -} --- Result: ["E1-1", "B2-2", "E3-3"] -``` - -## `reduce` Operation - -The `reduce` operation combines all elements of a collection into a single value using an accumulator function. - -### Syntax - -```sentrie -reduce collection from initialValue as accumulator, element, index block_expression -``` - -The `yield` statement evaluates the new `accumulator` value. The `index` parameter is optional. - -### Basic Examples - -```sentrie -let numbers: list[number] = [1, 2, 3, 4, 5] - --- Sum all numbers -let sum: number = reduce numbers from 0 as acc, num, idx { - yield acc + num -} --- Result: 15 - --- Find the maximum number -let max: number = reduce numbers from numbers[0] as acc, num, idx { - yield num > acc ? num : acc -} --- Result: 5 - --- Find the minimum number -let min: number = reduce numbers from numbers[0] as acc, num, idx { - yield num < acc ? num : acc -} --- Result: 1 - --- Count elements -let count: number = reduce numbers from 0 as acc, num, idx { - yield acc + 1 -} --- Result: 5 +```text +any collection as element, index { yield trinary } +all collection as element, index { yield trinary } +filter collection as element, index { yield trinary } +map collection as element, index { yield expr } +reduce collection from initial as acc, element, index { yield expr } +count collection +distinct collection ``` -### String Operations +Index parameter is optional in some forms. For maps, element is key-value or value depending on operation. -```sentrie -let words: list[string] = ["Hello", "World", "Sentrie"] +## Parameters --- Concatenate strings -let sentence: string = reduce words from "" as acc, word, idx { - yield acc + (idx == 0 ? "" : " ") + word -} --- Result: "Hello World Sentrie" +| Operation | Input | Output | Description | +| :--- | :--- | :--- | :--- | +| `any` | collection | bool/trinary | True if at least one element yields truthy. | +| `all` | collection | bool/trinary | True if all elements yield truthy. | +| `filter` | collection | same type | New collection of elements for which yield is truthy. | +| `map` | collection | list | New list of yield values. | +| `reduce` | collection, initial | type of initial | Fold: acc = initial, then acc = yield for each element. | +| `count` | collection | number | Number of elements. | +| `distinct` | collection | same type | New collection with duplicates removed. | --- Find longest word -let longest: string = reduce words from words[0] as acc, word, idx { - yield count(word) > count(acc) ? word : acc -} --- Result: "Sentrie" +**Returns:** As in table. Empty collection: `any` false, `all` true, `count` 0. Reduce with empty collection returns initial. --- Count total characters -let total_chars: number = reduce words from 0 as acc, word, idx { - yield acc + count(word) -} --- Result: 16 -``` +## Examples -### Complex Aggregations +### Basic Usage ```sentrie -shape Sale { - product!: string - quantity!: number - price!: number -} - -let sales: list[Sale] = [ - { product: "Laptop", quantity: 2, price: 999.99 }, - { product: "Mouse", quantity: 5, price: 29.99 }, - { product: "Keyboard", quantity: 3, price: 79.99 }, - { product: "Laptop", quantity: 1, price: 999.99 } -] - --- Calculate total revenue -let total_revenue: number = reduce sales from 0.0 as total_sale, sale, idx { - yield total_sale + (sale.quantity * sale.price) -} --- Result: 2 * 999.99 + 5 * 29.99 + 3 * 79.99 + 1 * 999.99 = 3399.89 - --- Count total items sold -let total_items: number = reduce sales from 0 as total_sale, sale, idx { - yield total_sale + sale.quantity -} --- Result: 2 + 5 + 3 + 1 = 11 - --- Find most expensive sale -let max_sale_value: number = reduce sales from 0.0 as sale, idx { - let sale_value: number = sale.quantity * sale.price - yield sale_value > sale_value ? sale_value : sale_value -} --- Result: 1999.98 (2 * 999.99) +let has_even: bool = any numbers as num, idx { yield num % 2 == 0 } +let all_even: bool = all numbers as num, idx { yield num % 2 == 0 } +let evens: list[number] = filter numbers as num, idx { yield num % 2 == 0 } +let doubled: list[number] = map numbers as num, idx { yield num * 2 } +let sum: number = reduce numbers from 0 as acc, num, idx { yield acc + num } +let n: number = count numbers +let uniq: list[number] = distinct numbers ``` -## `distinct` Operation - -The `distinct` operation removes duplicate elements from a collection, keeping only unique values. - -### Syntax +### Advanced Usage ```sentrie -distinct collection as left, right block_expression +let sum: number = reduce scores from 0 as acc, score, idx { yield acc + score } +let avg: number = sum / count scores ``` -The `yield` statement returns a `bool` value. If the predicate is truthy, the left and right values are considered the same and not included in the result. +## Behavior & Constraints -### Basic Examples +- Only valid on collections (lists, maps). Original collection is not modified. +- Block must yield once per iteration. Type of yield must match (trinary for any/all/filter predicate; expr for map/reduce). -```sentrie -let numbers: list[number] = [1, 2, 2, 3, 3, 3, 4, 5] - --- Remove duplicates (default behavior) -let unique_numbers: list[number] = distinct numbers as left, right { - yield left == right -} --- Result: [1, 2, 3, 4, 5] - -let colors: list[string] = ["red", "blue", "green", "red", "yellow", "blue"] - --- Remove duplicate colors -let unique_colors: list[string] = distinct colors as left, right { - yield left == right -} --- Result: ["red", "blue", "green", "yellow"] -``` - -### Custom Equality Logic - -```sentrie -shape Person { - name!: string - age: number -} - -let people: list[Person] = [ - { name: "Alice", age: 25 }, - { name: "Bob", age: 30 }, - { name: "Alice", age: 25 }, -- Duplicate - { name: "Charlie", age: 35 }, - { name: "Bob", age: 30 } -- Duplicate -] - --- Remove duplicates based on name -let unique_by_name: list[Person] = distinct people as left, right { - yield left.name == right.name -} --- Result: Alice, Bob, Charlie - --- Remove duplicates based on age -let unique_by_age: list[Person] = distinct people as left, right { - yield left.age == right.age -} --- Result: Alice (25), Bob (30), Charlie (35) -``` - -### Complex Deduplication - -```sentrie -shape Product { - id!: string - name!: string - price!: number - category!: string -} - -let products: list[Product] = [ - { id: "1", name: "Laptop", price: 999.99, category: "Electronics" }, - { id: "2", name: "Mouse", price: 29.99, category: "Electronics" }, - { id: "1", name: "Laptop", price: 999.99, category: "Electronics" }, -- Duplicate - { id: "3", name: "Book", price: 19.99, category: "Books" }, - { id: "4", name: "Laptop", price: 1099.99, category: "Electronics" } -- Different price -] - --- Remove duplicates by ID -let unique_by_id: list[Product] = distinct products as left, right { - yield left.id == right.id -} --- Result: Removes the duplicate laptop with same ID - --- Remove duplicates by name and category -let unique_by_name_category: list[Product] = distinct products as left, right { - yield left.name == right.name and left.category == right.category -} --- Result: Keeps both laptops (different prices) but removes exact duplicates -``` - -## Chaining Operations - -Collection operations can be chained together to create complex data transformations: - -```sentrie -shape Employee { - name!: string - age: number - department!: string - salary!: number - years_experience: number -} +## Constraints & Edge Cases -let employees: list[Employee] = [ - { name: "Alice", age: 25, department: "Engineering", salary: 95000.0, years_experience: 5 }, - { name: "Bob", age: 17, department: "Engineering", salary: 75000.0, years_experience: 3 }, - { name: "Charlie", age: 30, department: "Marketing", salary: 110000.0, years_experience: 8 }, - { name: "Diana", age: 28, department: "Engineering", salary: 85000.0, years_experience: 4 }, - { name: "Eve", age: 16, department: "Marketing", salary: 65000.0, years_experience: 2 } -] - --- Get names of adult engineers with high salary -let senior_engineers: list[string] = map filter filter employees as emp, idx { - yield emp.age >= 18 -} as emp, idx { - yield emp.department == "Engineering" -} as emp, idx { - yield emp.salary >= 90000.0 -} as emp, idx { - yield emp.name -} --- Result: ["Alice"] - --- Calculate average salary of experienced employees -let experienced_employees: list[Employee] = filter employees as emp, idx { - yield emp.years_experience >= 5 -} - -let total_salary: number = reduce experienced_employees from 0.0 as emp, idx { - yield emp.salary -} - -let avg_salary: number = total_salary / count experienced_employees --- Result: (95000.0 + 110000.0) / 2 = 102500.0 - --- Get unique departments -let departments: list[string] = map employees as emp, idx { - yield emp.department -} - -let unique_departments: list[string] = distinct departments as left, right { - yield left == right -} --- Result: ["Engineering", "Marketing"] -``` - -## Performance Considerations - -- **Lazy Evaluation**: Collection operations are evaluated lazily when possible -- **Memory Efficiency**: Operations create new collections rather than modifying existing ones -- **Optimization**: The Sentrie runtime optimizes common operation patterns -- **Index Access**: The `index` parameter is available for use when necessary - -## Best Practices - -### Use Meaningful Variable Names - -```sentrie --- Good -let adult_users: list[User] = filter users as user, idx { - yield user.age >= 18 -} - --- Avoid -let filtered: list[User] = filter users as user, idx { - yield user.age >= 18 -} -``` - -### Chain Operations Readably - -```sentrie --- Good: Each operation on its own line -let result: list[string] = map filter filter users as user, idx { - yield user.age >= 18 -} as user, idx { - yield user.department == "Engineering" -} as user, idx { - yield user.name -} - --- Avoid: All operations on one line -let result: list[string] = map filter filter users as user, idx { yield user.age >= 18 } as user, idx { yield user.department == "Engineering" } as user, idx { yield user.name } -``` - -### Use Appropriate Operations - -```sentrie --- Use count with filter instead of reduce for counting -let adult_users: list[User] = filter users as user, idx { - yield user.age >= 18 -} -let adult_count: number = count adult_users - --- Use distinct for deduplication -let unique_names: list[string] = distinct names as left, right { - yield left == right -} -``` - -### Handle Edge Cases - -```sentrie --- Check for empty collections -let safe_count: number = count users -let has_users: bool = safe_count > 0 - --- Handle optional fields safely -let emails: list[string] = map filter users as user, idx { - yield user.email is defined -} as user, idx { - yield user.email -} -``` +- Empty collection: `any` → false, `all` → true, `filter`/`map`/`distinct` → empty, `count` → 0, `reduce` → initial. +- Reduce: first iteration uses initial as acc; subsequent use previous yield as acc. diff --git a/src/content/docs/reference/constraints.md b/src/content/docs/reference/constraints.md index 04f289e..d81ada2 100644 --- a/src/content/docs/reference/constraints.md +++ b/src/content/docs/reference/constraints.md @@ -1,129 +1,50 @@ --- title: Constraints -description: Constraints provide a way to validate values against specific rules in Sentrie. +description: Constraint syntax and validation for types (e.g. @min, @max, @email). --- -Constraints provide a way to validate values against specific rules. They are applied using the `@` syntax and are intrinsically tied to the type of the field they validate. +# Constraints -## Basic Usage +Constraints validate values at runtime using the `@` syntax on types. They apply to primitives, collection elements, and shape fields. Validation failure aborts evaluation. -```text -let u: number @min(0) @max(100) = 50 -``` - -:::note -In the example above, `number @min(0) @max(100)` forms the type of the variable `u`. Constraints validate values at runtime. If a value doesn't meet the constraint requirements, validation will fail: +## Syntax ```text -let u: number @min(0) @max(100) = 101 -- This will fail validation +type @constraint1(args) @constraint2 ``` -::: +Examples: `number @min(0) @max(100)`, `string @email`, `list[string @one_of("a","b")]`. -## Collection Constraints +## Parameters -You can apply constraints to elements within collections: +| Category | Constraints (examples) | +| :--- | :--- | +| Numeric | `@eq`, `@neq`, `@gt`, `@lt`, `@in`, `@not_in`, `@range(min,max)`, `@multiple_of`, `@even`, `@odd`, `@positive`, `@negative`, `@non_negative`, `@non_positive`, `@finite`, `@infinite`, `@nan` | +| String | `@length`, `@minlength`, `@maxlength`, `@regexp`, `@starts_with`, `@ends_with`, `@has_substring`, `@not_has_substring`, `@email`, `@url`, `@uuid`, `@alphanumeric`, `@alpha`, `@numeric`, `@lowercase`, `@uppercase`, `@trimmed`, `@not_empty`, `@one_of`, `@not_one_of` | +| List | `@not_empty` | -```text -let permissions: list[string @one_of("read", "write", "delete")] = ["read", "write"] -``` +**Returns:** N/A. Constraint failure raises an error and aborts evaluation. + +## Examples -For better readability, consider using [shapes](/reference/shapes): +### Basic Usage ```text +let u: number @min(0) @max(100) = 50 shape Permission string @one_of("read", "write", "delete") - -let permissions: list[Permission] = ["read", "write"] ``` -:::tip -Use contraints for runtime validation of data. - -```sentrie -shape Positive100 number @min(0) @max(100) -let y = 50 -let c:Positive100 = y -``` - -In the above example, the value of `y` is validated against the constraints before being assigned to `c`. -::: - -## Available Constraints - -### Numeric Constraints (`number`) - -| Constraint | Description | -| --------------------- | ----------------------------------------------- | -| `@eq(value)` | Value must equal the specified value | -| `@neq(value)` | Value must not equal the specified value | -| `@gt(value)` | Value must be greater than the specified value | -| `@lt(value)` | Value must be less than the specified value | -| `@in(values...)` | Value must be in the specified list | -| `@not_in(values...)` | Value must not be in the specified list | -| `@range(min, max)` | Value must be between min and max (inclusive) | -| `@multiple_of(value)` | Value must be a multiple of the specified value | -| `@even()` | Value must be even | -| `@odd()` | Value must be odd | -| `@positive()` | Value must be positive | -| `@negative()` | Value must be negative | -| `@non_negative()` | Value must be non-negative (≥ 0) | -| `@non_positive()` | Value must be non-positive (≤ 0) | - -### Number-Specific Constraints - -| Constraint | Description | -| ------------- | ---------------------- | -| `@finite()` | Value must be finite | -| `@infinite()` | Value must be infinite | -| `@nan()` | Value must be NaN | - -### String Constraints - -| Constraint | Description | -| ------------------------------- | --------------------------------------------------- | -| `@length(value)` | String must be exactly the specified length | -| `@minlength(value)` | String must be at least the specified length | -| `@maxlength(value)` | String must be at most the specified length | -| `@regexp(pattern)` | String must match the specified regular expression | -| `@starts_with(substring)` | String must start with the specified substring | -| `@ends_with(substring)` | String must end with the specified substring | -| `@has_substring(substring)` | String must contain the specified substring | -| `@not_has_substring(substring)` | String must not contain the specified substring | -| `@email()` | String must be a valid email address | -| `@url()` | String must be a valid URL | -| `@uuid()` | String must be a valid UUID | -| `@alphanumeric()` | String must contain only alphanumeric characters | -| `@alpha()` | String must contain only letter characters | -| `@numeric()` | String must contain only numeric characters | -| `@lowercase()` | String must be lowercase | -| `@uppercase()` | String must be uppercase | -| `@trimmed()` | String must not have leading or trailing whitespace | -| `@not_empty()` | String must not be empty | -| `@one_of(values...)` | String must be one of the specified values | -| `@not_one_of(values...)` | String must not be one of the specified values | - -### List Constraints - -| Constraint | Description | -| -------------- | ---------------------- | -| `@not_empty()` | List must not be empty | - -## Type Conversion with Constraints - -When converting between types using the `cast .. as` construct, the result is validated against the new type constraints before returning the result. +### Advanced Usage ```text -let u: number = cast "50" as number +let permissions: list[string @one_of("read", "write", "delete")] = ["read", "write"] ``` -```text -let u: string = cast 50 as string -``` +## Behavior & Constraints -```text -let u: bool = cast "true" as bool -``` +- Constraints are checked at runtime when a value is assigned or cast to the constrained type. +- Order of application is defined by the runtime. All specified constraints must pass. -```text -let u: document = cast { "name": "John", "age": 30 } as document -``` +## Constraints & Edge Cases + +- Failing constraint validation aborts evaluation immediately. Use shapes to reuse constrained types. diff --git a/src/content/docs/reference/facts.md b/src/content/docs/reference/facts.md index f460c97..cb6bf5d 100644 --- a/src/content/docs/reference/facts.md +++ b/src/content/docs/reference/facts.md @@ -1,247 +1,55 @@ --- title: Facts -description: Facts are input data declarations that provide external values to policy evaluation. +description: "Fact declaration syntax: required/optional, type, alias, default." --- -Facts are named input values that can be injected into policy evaluation. They serve as the primary mechanism for providing external data to policies, enabling them to make decisions based on runtime information. +# Facts -:::note[Note] -If a policy uses facts, then they must be declared at the top of the policy. Facts declarations can only be preceded by other facts or comments. -::: +Facts are named inputs to a policy. They are declared at the top of the policy (after comments and other facts). Required by default; use `?` for optional. Only optional facts may have a default. Facts are non-nullable. -## Fact Declaration +## Syntax -### Basic Syntax - -```sentrie --- Required fact (default behavior) -fact : ('as' )? - --- Optional fact (can have default) -fact ?: ('as' )? ('default' )? -``` - -:::note[Note] -- The `exposed_name` (via `as`) is the name that will be used to reference the fact when the policy is evaluated -- If no `as` clause is provided, the fact name itself is used as the exposed name -- The `default` clause is only allowed for optional facts (marked with `?`) -- Facts are **required by default** - use `?` to make them optional -- Facts are **always non-nullable** - null values are not allowed -::: - -### Required vs Optional Facts - -```sentrie --- Required fact (must be provided during execution, default behavior) -fact user: User as user - --- Optional fact (can be omitted, marked with ?) -fact context?: Context as context default { "key": "value" } +```text +fact name : type [ as alias ] [ default expr ] -- required +fact name? : type [ as alias ] [ default expr ] -- optional ``` -:::note[Important] -- **Facts are required by default** - If no modifier is specified, the fact must be provided during execution -- **Use `?` to mark facts as optional** - Optional facts can be omitted, but if provided, they must be non-null -- **Facts are always non-nullable** - Null values are not allowed for facts -- **Required facts cannot have default values** - Only optional facts can have defaults -::: - -## Fact Types and Constraints +## Parameters -### Primitive Types +| Part | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| `name` | identifier | Yes | Declaration name. | +| `name?` | — | No | `?` makes the fact optional. | +| `type` | shape/primitive | Yes | Type of the fact value. | +| `as alias` | identifier | No | Name used in the policy body; default is `name`. | +| `default expr` | expression | No | Only for optional facts; used when fact is omitted. | -```sentrie --- Required facts (must be provided) -fact userId: string as id -fact isActive: bool as active - --- Optional facts with defaults -fact score?: number as points default 0 -fact price?: number as cost default 0.0 -fact name?: string as userName default "anonymous" -``` - -### Collection Types +**Returns:** N/A (declaration). At evaluation time, fact names (or aliases) are bound to the provided JSON input. -```sentrie --- Required collection facts -fact permissions: list[string] as userPermissions - --- Optional collection facts with defaults -fact metadata?: map[string] as userMetadata default {} -fact coordinates?: record[number, number] as location default [ 0.0, 0.0 ] -``` +## Examples -### Shape Types +### Basic Usage ```sentrie -shape User { - id!: string - role!: string - permissions!: list[string] -} - --- Required shape fact fact user: User as currentUser - --- Optional shape fact with default -fact user?: User as currentUser default { "id": "", "role": "guest", "permissions": [] } -``` - -## Fact Modifiers - -### Required Facts (Default) - -Facts are **required by default**. They must be provided during policy execution, and they cannot have default values. - -```sentrie --- Required fact - must be provided during evaluation -fact user: User as user - --- This will cause an error: required facts cannot have defaults --- fact user: User as user default { "id": "", "role": "guest" } -- Error! -``` - -### Optional Facts (`?`) - -Use the `?` operator to mark a fact as optional. Optional facts can be omitted during execution, and they can have default values. - -```sentrie --- Optional fact - can be omitted during evaluation -fact context?: Context as context - --- Optional fact with default value -fact context?: Context as context default { "key": "value" } - --- If not provided, the default value will be used (if specified) --- If no default is provided and the fact is omitted, it simply won't be available -``` - -:::warning[Important] -- Facts are **always non-nullable** - Even if a fact is optional, if it is provided, it cannot be null -- The `!` operator is **not supported** for facts - facts are always non-nullable by design -- Only **optional facts** (`?`) can have default values -::: - -## Default Values - -### Literal Defaults - -Default values can only be used with **optional facts** (marked with `?`). - -```sentrie --- String defaults (optional facts) -fact name?: string as userName default "anonymous" - --- Numeric defaults (optional facts) -fact count?: number as itemCount default 0 -fact rate?: number as interestRate default 0.05 - --- Boolean defaults (optional facts) -fact enabled?: bool as isEnabled default true - --- Collection defaults (optional facts) -fact tags?: list[string] as itemTags default [] -fact config?: map[string] as settings default {} -``` - -:::warning[Error] -Required facts cannot have default values. This will cause a compilation error: - -```sentrie --- This will cause an error -fact name: string as userName default "anonymous" -- Error: required fact cannot have default -``` -::: - -### Shape Defaults - -```sentrie -shape Product { - id!: string - name!: string - price!: number -} - --- Optional fact with shape default -fact product?: Product as currentProduct default { - "id": "", - "name": "Unknown", - "price": 0.0 -} -``` - -## Fact Injection - -### During Policy Import - -```sentrie --- Import policy with fact injection -rule authResult = import decision of canAccess - from com/example/auth/userAccess - with user as { "id": "123", "role": "admin", "permissions": ["read", "write"] } +fact context?: Context as ctx default {} ``` -### Multiple Fact Injection +### Advanced Usage ```sentrie --- Inject multiple facts -rule result = import decision of processOrder - from com/example/orders/orderProcessing - with user as currentUser - with order as orderData - with context as requestContext -``` - -## Type Safety - -### Runtime Validation - -```sentrie --- Type validation: This will cause a type error -fact user?: User as user default { "id": 123 } -- Error: string expected, got number - --- Correct usage -fact user?: User as user default { "id": "123" } - --- Null validation: Facts cannot be null --- This will cause a runtime error if null is provided -fact user: User as user -- If null is provided, error: "fact 'user' cannot be null" -``` - -## Best Practices - -### Use Descriptive Names - -```sentrie --- Good: Clear, descriptive fact names -fact currentUser?: User as user default { "id": "", "role": "guest" } -fact orderData?: Order as order default { "id": "", "items": [] } - --- Avoid: Generic or unclear names -fact data?: User as d default { "id": "", "role": "guest" } -``` - -### Provide Sensible Defaults - -```sentrie --- Good: Meaningful default values for optional facts -fact user?: User as user default { "id": "anonymous", "role": "guest", "permissions": [] } - --- Avoid: Confusing or invalid defaults -fact user?: User as user default { "id": "", "role": "invalid", "permissions": null } -- null not allowed +fact userId: string as id +fact config?: document as settings default { "env": "production" } ``` -### Use Required Facts Appropriately +## Behavior & Constraints -```sentrie --- Good: Use required facts for data that must always be provided -fact userId: string as id -- Must be provided, no default allowed +- Facts must be declared before rules; only facts or comments may precede fact declarations. +- Required facts must be supplied at evaluation or evaluation fails. +- Optional facts may be omitted; if provided they must be non-null. Default is used when omitted. +- Null is not allowed for any fact. --- Good: Use optional facts with defaults for data that has sensible fallbacks -fact context?: Context as ctx default { "environment": "production" } +## Constraints & Edge Cases --- Avoid: Making everything required when defaults would be appropriate -fact userId: string as id -- If this often has a default, consider making it optional -fact userId?: string as id default "anonymous" -- Better if default makes sense -``` +- Fact name in `with` for imports must match the **alias** (or name if no `as`) in the target policy. +- Type of the supplied value must match the fact type (and constraints) or evaluation fails. diff --git a/src/content/docs/reference/functions.md b/src/content/docs/reference/functions.md index 0aeaf8c..08c7dbb 100644 --- a/src/content/docs/reference/functions.md +++ b/src/content/docs/reference/functions.md @@ -1,395 +1,54 @@ --- -title: Using Functions -description: How to call and use functions in Sentrie, including built-in functions and TypeScript module functions. +title: Functions +description: Function call syntax and TypeScript module usage; memoization. --- -Functions are a fundamental part of Sentrie that allow you to perform operations, transform data, and extend functionality. Sentrie supports two types of functions: built-in functions that are always available, and TypeScript module functions that you import using the `use` statement. +# Functions -Functions in Sentrie enable you to: +Functions are called with `name(args...)` or `alias.name(args...)` for imported modules. Sentrie has no built-in global functions; all functions come from TypeScript modules imported with `use`. -- Perform common operations using built-in reusable utilities -- Extend functionality by importing functions from TypeScript modules -- Optimize performance with function **memoization** +## Syntax -## Function Call Syntax - -### Basic Syntax - -Functions are called using the standard function call syntax with parentheses: - -```sentrie -let name = functionName(argument1, argument2, ...) +```text +functionName(arg1, arg2, ...) +alias.functionName(arg1, arg2, ...) ``` -### Module Functions +Import: `use { fn1, fn2 } from source [ as alias ]` -Functions imported from TypeScript modules are called using the module alias followed by a dot: +## Parameters -```sentrie -namespace com/example/auth +| Element | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| `source` | `@sentrie/module` or `"./file.ts"` | Yes | Built-in (no quotes) or relative path (quotes). | +| `as alias` | identifier | No | Default alias is last path segment (e.g. `hash` for `@sentrie/hash`). | +| args | expressions | Per function | Typed by the module; see [TypeScript modules](/reference/typescript_modules/). | -policy mypolicy { - use { sha256, now } from @sentrie/hash as hash - use { parse } from @sentrie/json as json +**Returns:** Per function; see module docs. Invalid args or runtime errors abort evaluation. - fact data: string +## Examples - rule processData = default false { - let hashValue = hash.sha256(data) - let currentTime = hash.now() - let parsed = json.parse(data) - yield hashValue != "" and currentTime > 0 - } - - export decision of processData -} -``` - -:::note -If no alias is specified, the default alias is the last part of the module path: +### Basic Usage ```sentrie use { sha256 } from @sentrie/hash --- Default alias is "hash", so you call: hash.sha256(data) -``` - -::: - -## TypeScript Module Functions - -Sentrie allows you to import and use functions from TypeScript modules, including built-in `@sentrie/*` modules and your own local TypeScript files. This provides extensive functionality for cryptography, data manipulation, time operations, and more. - -### Importing Functions - -Functions are imported using the `use` statement: - -```sentrie -use { function1, function2 } from @sentrie/module as alias -``` - -### Built-in Modules - -Sentrie provides comprehensive built-in TypeScript modules under the `@sentrie/*` namespace: - -```sentrie -namespace com/example/crypto - -policy security { - use { sha256, md5 } from @sentrie/hash - use { now, parse } from @sentrie/time as time - use { isValid } from @sentrie/json as json - - fact password: string - fact timestamp: number - - rule validatePassword = default false { - let hash = sha256(password) - let currentTime = time.now() - yield hash != "" and currentTime > timestamp - } - - export decision of validatePassword -} -``` - -### Local TypeScript Modules - -You can also import functions from your own TypeScript files: - -```sentrie -namespace com/example/utils - -policy processing { - 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 -} -``` - -For detailed information about available TypeScript modules and their functions, see [Using TypeScript](/reference/using-typescript) and [Built-in TypeScript Modules](/reference/typescript_modules). - -## Function Memoization - -Function memoization allows you to cache the results of function calls to improve performance for expensive operations. Memoization is particularly useful for functions that perform heavy computations or external calls. - -### Syntax - -Memoization is enabled by appending `!` to a function call: - -```sentrie -functionName(args...)! -- Default TTL (5 minutes) -functionName(args...)!300 -- Custom TTL in seconds -``` - -### Default TTL - -When you omit the TTL value, memoization uses a default time-to-live of **5 minutes** (300 seconds): - -```sentrie -let result = expensiveFunction(data)! -- Cached for 5 minutes -``` - -### Custom TTL - -You can specify a custom TTL in seconds: - -```sentrie -let result = expensiveFunction(data)!60 -- Cached for 60 seconds -let result = expensiveFunction(data)!3600 -- Cached for 1 hour -``` - -### Memoization Behavior - -- **TypeScript Module Functions**: Memoization is fully supported and provides performance benefits for expensive operations -- **Built-in Functions**: While the syntax is supported, built-in functions are not actually memoized as they are already optimized and fast enough that caching provides minimal benefit - -### Example - -```sentrie -namespace com/example/processing - -policy dataProcessing { - use { sha256 } from @sentrie/hash - use { complexCalculation } from "./heavy-compute.ts" as compute - - fact data: string - - rule processData = default false { - -- Memoize expensive computation for 10 minutes - let hash = sha256(data)!600 - let result = compute.complexCalculation(data)!600 - - yield hash != "" and result > 0 - } - - export decision of processData -} -``` - -:::tip -Use memoization for functions that: - -- Perform expensive computations -- Make external API calls (if supported) -- Process large amounts of data -- Are called multiple times with the same arguments - -Avoid memoization for functions that: - -- Are already very fast (like built-in functions) -- Need to return fresh data on every call -- Have side effects that must execute each time - ::: - -## Using Functions in Rules and Let Declarations - -Functions can be used anywhere expressions are allowed, including in `let` declarations and rule bodies: - -```sentrie -namespace com/example/complex - -policy example { - use { sha256 } from @sentrie/hash - use { now } from @sentrie/time as time - - fact userData: map[string]any - fact items: list[string] - - -- Policy-level let with function calls - let itemCount = count(items) - let timestamp = time.now() - - rule processUser = default false { - -- Rule-level let with function calls - let hash = sha256(userData.id) - let merged = merge(userData, {"processed": true}) - - yield hash != "" and itemCount > 0 - } - - export decision of processUser -} -``` - -## Built-in Functions - -Sentrie provides a set of built-in functions that are always available without any imports. These functions are optimized for performance and are commonly used operations. - -### `count(value) => number` - -
-Returns the number of elements in a collection or the length of a string. - -The `count` function accepts a list, map, or string and returns the number of elements or characters. - -**Examples:** - -- `count([1, 2, 3])` → `3` -- `count("hello")` → `5` -- `count({"a": 1, "b": 2})` → `2` - -```sentrie -let items: list[string] = ["apple", "banana", "cherry"] -let itemCount = count(items) -- Returns 3 -``` - -
- -### `merge(map1, map2) => map[string]any` - -
-Recursively merges two maps into a new map. - -The `merge` function combines two maps, with values from the second map overwriting values from the first map. Nested maps are merged recursively rather than being replaced entirely. - -**Examples:** - -- `merge({"a": 1}, {"b": 2})` → `{"a": 1, "b": 2}` -- `merge({"a": {"x": 1}}, {"a": {"y": 2}})` → `{"a": {"x": 1, "y": 2}}` - -```sentrie -let userData = {"name": "Alice", "age": 30} -let additionalData = {"age": 31, "role": "admin"} -let combined = merge(userData, additionalData) --- Returns {"name": "Alice", "age": 31, "role": "admin"} -``` - -
- -### `error(format, args...) => error` - -
-Short-circuits execution and returns an error with a formatted message. - -The `error` function immediately stops execution and returns an error. It supports format strings similar to `fmt.Printf` in Go. If only one argument is provided, it's treated as the error message directly. - -**Examples:** - -- `error("Access denied")` -- `error("Invalid value: %v", value)` -- `error("User %s not found", username)` - -```sentrie -rule validateAccess = default false when user.role is defined { - if user.role != "admin" { - error("Access denied: user must be admin") - } - yield true -} -``` - -
- -### `as_list(value) => list[any]` - -
-Normalizes "one-or-many" inputs by wrapping non-list values in a single-element list. - -The `as_list` function takes a single value and ensures it's a list. If the input is already a list, it returns it unchanged. If the input is not a list, it wraps it in a single-element list. - -**Examples:** - -- `as_list(42)` → `[42]` -- `as_list("hello")` → `["hello"]` -- `as_list([1, 2, 3])` → `[1, 2, 3]` - -```sentrie -let single_value = 42 -let as_list_value = as_list(single_value) -- Returns [42] - -let already_list = [1, 2, 3] -let unchanged = as_list(already_list) -- Returns [1, 2, 3] -``` - -**Note:** If the input contains `undefined` values, the function returns `undefined`. - -
- -### `flatten(list, depth?) => list[any]` - -
-Flattens nested lists to a controlled depth. - -The `flatten` function takes a list and optionally a depth parameter, and flattens nested lists up to the specified depth. The default depth is 1 if not specified. - -**Examples:** - -- `flatten([[1, 2], [3, 4]])` → `[1, 2, 3, 4]` (default depth 1) -- `flatten([[1, 2], [3, 4]], 1)` → `[1, 2, 3, 4]` -- `flatten([[[1, 2]], [[3, 4]]], 2)` → `[1, 2, 3, 4]` -- `flatten([1, 2, 3], 0)` → `[1, 2, 3]` (no flattening) - -```sentrie -let nested = [[1, 2], [3, 4], [5]] -let flattened = flatten(nested) -- Returns [1, 2, 3, 4, 5] - -let deeply_nested = [[[1, 2]], [[3, 4]]] -let flattened_deep = flatten(deeply_nested, 2) -- Returns [1, 2, 3, 4] -``` - -**Note:** If the input contains `undefined` values, the function returns `undefined`. - -
- -### `flatten_deep(list) => list[any]` - -
-Recursively flattens nested lists to arbitrary depth. - -The `flatten_deep` function takes a list and recursively flattens all nested lists, regardless of nesting depth. - -**Examples:** - -- `flatten_deep([[1, 2], [3, [4, 5]]])` → `[1, 2, 3, 4, 5]` -- `flatten_deep([[[1]], [[2, 3]], [4]])` → `[1, 2, 3, 4]` - -```sentrie -let deeply_nested = [[1, [2, [3, 4]]], [5, 6]] -let fully_flattened = flatten_deep(deeply_nested) -- Returns [1, 2, 3, 4, 5, 6] +use { now } from @sentrie/time as time +let h = sha256(data) +let t = time.now() ``` -**Note:** If the input contains `undefined` values, the function returns `undefined`. - -
- -### `normalise_list(value) => list[any]` - -
-Normalizes messy list inputs with one level of nesting. - -The `normalise_list` function first applies `as_list` to wrap non-list values, then flattens exactly one level of nesting. It errors if the input contains deeper than one level of nesting. - -**Examples:** - -- `normalise_list(42)` → `[42]` (wrapped, then no flattening needed) -- `normalise_list([1, 2, 3])` → `[1, 2, 3]` (already flat) -- `normalise_list([[1, 2], [3, 4]])` → `[1, 2, 3, 4]` (one level flattened) -- `normalise_list([[[1, 2]]])` → Error (deeper than one level) +### Advanced Usage ```sentrie -let mixed_input = [[1, 2], 3, [4, 5]] -let normalized = normalise_list(mixed_input) -- Returns [1, 2, 3, 4, 5] +use { calculateAge, validateEmail } from "./utils.ts" as utils +yield utils.calculateAge(user.birthDate) >= 18 and utils.validateEmail(user.email) ``` -**Note:** If the input contains `undefined` values, the function returns `undefined`. - -
+## Behavior & Constraints -:::note -Built-in functions are fast and lightweight. While they support memoization syntax (see [Function Memoization](#function-memoization)), they are not actually memoized as caching would provide minimal benefit for these operations. -::: +- Functions are memoized per (function, args) when applicable; repeated calls with same args may return cached result. +- Module scope: `use` is per policy; alias is used in that policy only. -## See Also +## Constraints & Edge Cases -- [Using TypeScript](/reference/using-typescript) - Learn how to import and use TypeScript modules -- [Built-in TypeScript Modules](/reference/typescript_modules) - Complete reference for all built-in modules -- [Intermediate Values](/reference/let) - Learn about `let` declarations where functions are commonly used -- [Rules](/reference/rules) - Learn how to use functions in rule bodies -- [Collection Operations](/reference/collection-operations) - Learn about collection-specific operations +- Missing or wrong-type arguments can cause runtime errors. See [Built-in TypeScript modules](/reference/typescript_modules/) and [Writing custom TypeScript modules](/extensibility/writing-custom-typescript-modules) for contracts. diff --git a/src/content/docs/reference/index.md b/src/content/docs/reference/index.md index b811747..cb9921f 100644 --- a/src/content/docs/reference/index.md +++ b/src/content/docs/reference/index.md @@ -1,875 +1,39 @@ --- -title: "Policy Language Reference" -description: "Complete reference for the Sentrie policy language syntax and features." +title: Policy Language Reference +description: Exhaustive dictionary of Sentrie language syntax and features. --- -This is the complete reference for the Sentrie policy language. It covers all language features, syntax, and semantics. +# Policy Language Reference -## Table of Contents +This section is a strict reference for the Sentrie policy language: syntax, types, operators, and constructs. For conceptual overviews, see [Language Concepts](/language-concepts/type-system-shapes). -- [Program Structure](#program-structure) -- [Namespaces](#namespaces) -- [Policies](#policies) -- [Rules](#rules) -- [Expressions](#expressions) -- [Primitives, Collections, Shapes, and Aliases](#primitives-collections-shapes-and-aliases) -- [Literals](#literals) -- [Operators](#operators) -- [Control Flow](#control-flow) -- [TypeScript Modules](#typescript-modules) -- [Facts and Variables](#facts-and-variables) -- [Exports and Imports](#exports-and-imports) -- [Exporting and Importing Rules](/reference/exporting-and-importing-rules) - Complete guide to rule exports and imports +## Syntax -## Program Structure - -A Sentrie program consists of: - -1. **Namespace declaration** (required) -2. **Top-level declarations** (policies, shapes) -3. **Comments** (anywhere) - -```text -namespace com/example/myapp - --- This is a comment -policy auth { - -- policy content -} - -shape User { - -- shape definition -} - -export shape User -- export shapes to allow visibility to other namespaces -``` - -## Namespaces - -Namespaces organize your policies and shapes hierarchically and prevent naming conflicts. - -### Syntax +A program is one namespace per file, then top-level policies and shapes: ```text namespace FQN +policy IDENT { ... } +shape IDENT { ... } +export shape IDENT ``` -Where `FQN` (Fully Qualified Name) is a slash-separated identifier: - -```text -namespace com/example/auth -namespace com/example/billing/v2 -namespace mycompany/policies/security -``` - -### Namespace statements - -A namespace can contain: - -- **policies**: `policy IDENT { ... }` -- **shapes**: `shape IDENT { ... }` -- **shape exports**: `export shape IDENT` - -### Rules - -- 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 - -Policies are containers for rules, facts, and other declarations. - -### Syntax - -```text -policy IDENT { - policyStatement* -} -``` - -### 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 - -```text -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 - -Rules are the core of Sentrie policies. They define what decisions to make based on input data. - -### Syntax - -```text -rule IDENT = (default expr)? (when expr)? blockExpr -``` - -### Components - -1. **Name**: `rule IDENT` -2. **Default** (optional): `default expr` - value when `when` is false or rule body doesn't yield -3. **When** (optional): `when expr` - condition that must be true -4. **Body**: `blockExpr` - block expression that must contain a `yield` statement - -### Examples - -```text --- Simple rule -rule allow = default false { - yield true -} - --- Rule with condition -rule canEdit = default false when user.role == "admin" { - yield true -} - --- Rule with default value -rule getPrice = default 0 when product.price is defined { - yield product.price -} - --- Rule with complex body -rule calculateDiscount = default 0 { - let basePrice = product.price - let discount = user.isPremium ? 0.1 : 0.05 - let finalPrice = basePrice * (1 - discount) - yield finalPrice -} -``` - -## Expressions - -Sentrie has a rich expression language with multiple operator types and precedence levels. - -### Precedence (highest to lowest) - -1. **Primary expressions**: literals, identifiers, function calls -2. **Unary operators**: `not`, `!` -3. **Arithmetic**: `*`, `/`, `%` -4. **Arithmetic**: `+`, `-` -5. **Comparison**: `<`, `<=`, `>`, `>=` -6. **Equality**: `==`, `!=` -7. **Logical AND**: `and` -8. **Logical XOR**: `xor` -9. **Logical OR**: `or` -10. **Ternary**: `? :` - -### Primary Expressions - -```text --- Literals -42 -3.14 -"hello" -true -false -unknown -null -[1, 2, 3] -{"key": "value"} - --- Identifiers -user -product.name -config.maxRetries - --- Function calls -time.now() -hash.sha256("data") -json.parse("{}") - --- Index access -users[0] -config["maxRetries"] - --- Field access -user.name -product.price - --- Parentheses -(1 + 2) * 3 -``` - -### Ternary Expressions - -```text -condition ? trueValue : falseValue - --- Examples -user.role == "admin" ? "full_access" : "limited_access" -age >= 18 ? "adult" : "minor" -``` - -### Block Expressions - -```text -{ - let variable = expression - -- other statements - yield result -} -``` - -## Primitives, Collections, Shapes, and Aliases - -Sentrie provides primitives, collections, shapes, and aliases for defining data structures. - -### Primitives - -- `number` - Numeric values (backed by float64) -- `string` - Text strings -- `trinary` - Trinary values (true/false/unknown) - - `bool` - Boolean values (true/false) - a special case of `trinary` -- `document` - JSON-like objects - -### Collections - -- `list[T]` - Lists of primitive T -- `map[T]` - Maps with string keys and primitive T values -- `record[T1, T2, ...]` - Tuples with specific primitives - -### 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) - -```text -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 - -Shapes can be composed from other shapes using the `with` keyword: - -```text -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 - -Constraints can be applied to primitives, collections, and shape fields: - -```text -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 - -You can create aliases using shapes: - -```text -shape Positive100 number @min(0) @max(100) - -let score: Positive100 = 50 -``` - -## Literals - -### String Literals - -```text -"hello world" -"escaped \"quotes\"" -"line 1\nline 2" -``` - -### Numeric Literals - -```text -42 -- Number --42 -- Negative number -3.14 -- Float --3.14 -- Negative float -1e5 -- Scientific notation -1.5e-3 -- Scientific notation with negative exponent -``` - -### Boolean and Trinary Literals - -```text -true -- Boolean true -false -- Boolean false -unknown -- Trinary unknown (neither true nor false) -``` - -### Collection Literals - -```text --- 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 - -```text -null -- Null value -``` - -## Operators - -### Arithmetic Operators - -```text -+ -- Addition -- -- Subtraction -* -- Multiplication -/ -- Division -% -- Modulo -``` - -### Comparison Operators - -```text -== -- Equality -!= -- Inequality -< -- Less than -<= -- Less than or equal -> -- Greater than ->= -- Greater than or equal -``` - -### Logical Operators - -```text -and -- Logical AND -or -- Logical OR -xor -- Logical XOR -not -- Logical NOT -! -- Logical NOT (alternative) -``` - -### Collection Operators - -```text -in -- Membership -not in -- Non-membership -contains -- Contains -not contains -- Does not contain -matches -- Pattern matching -not matches -- Pattern non-matching -``` - -### Shape Checking Operators - -```text -is defined -- Check if defined -is not defined -- Check if not defined -is empty -- Check if empty -is not empty -- Check if not empty -is -- Shape checking -``` - -### Quantifier Operators - -```text -any -- Any element satisfies condition -all -- All elements satisfy condition -filter -- Filter elements -map -- Transform elements -distinct -- Remove duplicates -reduce -- Reduce collection to single value -count -- Count elements -``` - -### Casting - -```text -cast -- Casting between primitives -``` - -Example: - -```text -let y = "99" -let x: number = cast y as number -``` - -## TypeScript Modules - -Sentrie supports importing functions from TypeScript modules, including built-in `@sentrie/*` modules and local TypeScript files. - -### Use Statement - -The `use` statement allows you to import functions from TypeScript modules: - -```text -use { function1, function2 } from @sentrie/module as alias -``` - -**Note:** 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 - -Built-in modules are prefixed with `@sentrie/`: - -```text -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 - -You can import TypeScript files from your policy pack using relative paths: - -```text -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 +## Reference Pages -- `@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/js` - JavaScript globals (Math, String, Number, Date, JSON, Array) -- `@sentrie/json` - JSON validation utility -- `@sentrie/jwt` - JSON Web Token decoding and verification -- `@sentrie/net` - Network and IP address utilities -- `@sentrie/regex` - Regular expression pattern matching -- `@sentrie/semver` - Semantic version comparison and validation -- `@sentrie/time` - Date and time manipulation -- `@sentrie/url` - URL parsing and manipulation -- `@sentrie/uuid` - UUID generation (v4, v6, v7) +**Core:** [Namespaces](/reference/namespaces) · [Policies](/reference/policies) · [Rules](/reference/rules) · [Facts](/reference/facts) · [Intermediate values (let)](/reference/let) -See the [Built-in TypeScript Modules](/reference/typescript_modules/) documentation for detailed information on each module. +**Types:** [Types and values](/reference/types-and-values) · [Constraints](/reference/constraints) · [Trinary](/reference/trinary) · [Shapes](/reference/shapes) -## Facts and Variables +**Operations:** [Arithmetic](/reference/arithmetic-operations) · [Boolean](/reference/boolean-operations) · [Collection operations](/reference/collection-operations) · [Functions](/reference/functions) · [Precedence](/reference/precedence) -### Facts - -Facts are named values that can be injected into policy evaluation: - -```text --- Required facts (must be provided) -fact userId: string as id -fact user: User as currentUser - --- Optional facts (can be omitted, marked with ?) -fact maxRetries?: number as limit default 3 -fact apiKey?: string as key default "" -fact config?: document as settings default {} -fact context?: Context as ctx default {"role": "guest"} -``` +**Other:** [Security and permissions](/reference/security-and-permissions) -Facts can have: +**TypeScript:** [Built-in TypeScript modules](/reference/typescript_modules/) (overview and per-module pages) -- **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) - -:::note[Important] - -- Facts are **required by default** - must be provided during execution -- Use `?` to mark facts as **optional** - can be omitted -- Facts are **always non-nullable** - null values are not allowed -- Only **optional facts** (`?`) can have default values - ::: - -### Variables - -Variables are local to a policy or rule: - -```text -let maxRetries = 3 -let adminRoles = ["admin", "super_admin"] -let userAge = user.birthDate ? calculateAge(user.birthDate) : 0 -let 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) - -:::note[Important] - -- `let` declarations are **scoped to their immediate block** (`{}`) -- `let` declarations **cannot be exported** - only rules can be exported -- `let` declarations are **immutable** - once declared, their value cannot be changed - ::: - -:::note -Read more on let declarations [here](/reference/let). -::: - -### Reduce Expressions - -Variables can be computed using `reduce` expressions: - -```text -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 - -### Exports - -Export rules to make them available for external evaluation: - -```text -export decision of ruleName -export decision of ruleName - attach attachmentName as expression - attach anotherAttachment as anotherExpression -``` - -Exports can include attachments that provide additional data: - -```text -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 null -``` - -### Imports - -Import rules from other policies: - -```text -rule importedRule = import decision ruleName from com/example/other/policy -rule importedRule = import decision ruleName from com/example/other/policy with param as value -``` - -## Comments - -Comments start with `--` and continue to the end of the line: - -```text --- This is a comment -rule allow = default false { -- Inline comment - yield true -} -``` - -## Error Handling - -Sentrie provides comprehensive error handling and validation: - -### Validation Errors - -```text --- This will cause a validation error -rule invalid = default false { - yield "string" + 42 -- Cannot add string and number -} -``` - -### Constraint Violations - -```text --- This will cause a constraint violation -rule invalid = default false { - let age: number @min(0) @max(150) = -5 -- Age constraint violation - yield age > 0 -} -``` - -### Undefined Values - -Accessing non-existent fields returns `undefined` rather than causing an error: - -```text -rule example = default false { - let value = user.nonexistent.field -- Returns undefined - yield value -- undefined -} -``` - -Any operation on `undefined` will also yield `undefined`: - -```text -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: - -```text -rule example = default false when user.nonexistent.field is defined { - yield user.nonexistent.field -} -``` - -## Best Practices - -### 1. Use Clear Names - -```text --- Good -rule canUserEditPost = default false when user.role == "admin" { - yield true -} - --- Bad -rule x = default false when a == "b" { - yield true -} -``` - -### 2. Organize by Namespace - -```text -namespace com/example/auth -namespace com/example/billing -namespace com/example/analytics -``` - -### 3. Use Facts for Configuration - -```text --- Required facts (must be provided) -fact maxLoginAttempts: number as limit - --- Optional facts with defaults -fact sessionTimeout?: number as timeout default 3600 -fact retryCount?: number as retries default 3 -``` - -### 4. Validate Inputs - -```text -rule validateUser = default false when user is defined { - yield user.id is defined and user.id != "" -} -``` - -### 5. Use Shapes for Validation - -```text -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 - -```text --- Use built-in modules for common operations -use { sha256 } from @sentrie/hash -use { now } from @sentrie/time as time -use { parse } from @sentrie/json as json -``` - -## Examples - -### Simple Authorization - -```text -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 - -```text -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 - -```text -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/js 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 -} -``` +**Composition:** [Policy composition](/language-concepts/policy-composition) (export/import) · [Writing custom TypeScript modules](/extensibility/writing-custom-typescript-modules) -## See Also +## Behavior & Constraints -- [Using TypeScript](/reference/using-typescript) - Complete guide to using TypeScript in Sentrie -- [Built-in TypeScript Modules](/reference/typescript_modules/) - Reference for all built-in modules -- [Policies](/reference/policies) - Detailed information about policies -- [Rules](/reference/rules) - Detailed information about rules -- [Facts](/reference/facts) - Detailed information about facts -- [Let Declarations](/reference/let) - Detailed information about let declarations -- [Shapes](/reference/shapes) - Detailed information about shapes +- One namespace per file; namespace must be the first statement (comments allowed before). +- Policies must export at least one rule to be executable or importable. +- All inputs and outputs in the reference are explicitly typed; edge cases are listed on each feature page. diff --git a/src/content/docs/reference/let.md b/src/content/docs/reference/let.md index 31b81f5..8312f6f 100644 --- a/src/content/docs/reference/let.md +++ b/src/content/docs/reference/let.md @@ -1,509 +1,54 @@ --- -title: Intermediate Values -description: Let declarations are containers for intermediate values within blocks. They are scoped to their immediate block and cannot be exported. +title: Intermediate Values (let) +description: let declaration syntax, scoping, and immutability. --- -`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. +# Intermediate Values (let) -:::note[Important] +`let` binds a name to an expression inside a block. It is scoped to that block, immutable, and cannot be exported. Used for intermediate calculations in policies and rules. -`let` declarations are: +## Syntax -- **Scoped to their immediate block** (`{}`) -- **Cannot be exported** - only rules can be exported -- **Immutable** - once declared, their value cannot be changed -- **Used for intermediate calculations** within a policy or rule block - -::: - -## Let Declaration Syntax - -### Basic Syntax - -```sentrie -let = - --- With optional type annotation -let : = -``` - -### Simple Examples - -```sentrie --- 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 - --- With type annotations and constraints -let count: number @min(0) @max(100) = 50 -let name: string @minlength(3) @maxlength(20) = "example" - --- can be shapes as well -let user: User = { name: "John Doe", age: 30 } -``` - -## Block Scoping - -`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 - -### Policy-Level `let` Declarations - -```sentrie -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 -} -``` - -### Block-Level `let` Declarations - -```sentrie -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 -} -``` - -### Nested Block Scoping - -```sentrie -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 -} -``` - -## Cannot Be Exported - -`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. - -### Incorrect Usage - -```sentrie -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 -} -``` - -### Correct Usage - -```sentrie -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 -} -``` - -## Immutability - -`let` declarations are **immutable** - once a value is assigned to a `let` declaration, it cannot be changed or reassigned within the same scope. - -### Single Assignment - -Each `let` declaration can only be assigned once. Attempting to reassign a `let` declaration will result in an error: - -```sentrie -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 -} +```text +let name = expr +let name : type = expr ``` -### Creating New Values - -If you need to compute a new value based on an existing `let` declaration, create a new `let` declaration: - -```sentrie -namespace com/example/calculations - -policy pricing { - fact basePrice: number as price +## Parameters - -- Initial calculation - let subtotal = price * 1.0 +| Part | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| `name` | identifier | Yes | Variable name. | +| `type` | type | No | Optional annotation; validates assignment. | +| `expr` | expression | Yes | Initial value. | - rule calculateTotal = default 0 { - -- Create new let declarations for subsequent calculations - let withTax = subtotal * 1.08 - let withDiscount = withTax * 0.9 - let finalPrice = withDiscount +**Returns:** N/A (binding). The name evaluates to the bound value in scope. - yield finalPrice - } +## Examples - export decision of calculateTotal -} -``` - -### Benefits of Immutability - -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 +### Basic Usage ```sentrie -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 - -Type annotations can be used with `let` declarations to help with type safety and readability. - -### Without Type Annotation - -```sentrie --- 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] -``` - -### With Type Annotation - -```sentrie --- 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 - -```sentrie --- 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"] -``` - -:::note -While type annotations are optional, they are recommended for better readability and to catch type errors early. For reference, see [Types and Values](/reference/types-and-values) and [Shapes](/reference/shapes) for more information. -::: - -## Using Let in Complex Expressions - -`let` declarations are particularly useful for breaking down complex expressions: - -### Without Let (Hard to Read) - -```sentrie -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) -} -``` - -### With Let (More Readable) - -```sentrie -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 -} -``` - -## Using Let with TypeScript Functions - -`let` declarations work well with TypeScript functions imported via `use` statements: - -```sentrie -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 -} -``` - -## Using Let with Reduce Expressions - -`let` declarations can be used with `reduce` expressions for aggregations: - -```sentrie -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 -} -``` - -## Best Practices - -### Use Descriptive Names - -```sentrie --- 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) -``` - -### Break Down Complex Expressions - -```sentrie --- 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 -} -``` - -### Use Type Annotations for Clarity - -```sentrie --- Good: Explicit types make code clearer +let adminRoles = ["admin", "super_admin"] +let totalPrice = item.price * quantity 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"] ``` -### Keep Let Declarations Close to Usage +### Advanced Usage ```sentrie --- 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 - } -} -``` - -## Common Patterns - -### Conditional Values - -```sentrie -rule processUser = default false { - let userRole = user.role is defined ? user.role : "guest" - let accessLevel = userRole == "admin" ? "full" : "limited" - - yield accessLevel == "full" -} -``` - -### Intermediate Calculations - -```sentrie -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 -} +let count: number @min(0) @max(100) = 50 ``` -### Data Transformation +Block scope: `let` inside a rule body is only visible in that body; policy-level `let` is visible to all rules in the policy. -```sentrie -rule transformData = default false { - let normalizedName = user.name.lower() - let sanitizedEmail = user.email.trim() - let formattedRole = user.role.upper() +## Behavior & Constraints - yield normalizedName != "" and sanitizedEmail != "" -} -``` +- Scoped to the immediate block (`{}`). Policy-level: visible to all rules in the policy. +- Immutable: cannot be reassigned. +- Cannot be exported; only rules can be exported. +- Type annotation is optional; if present, value is validated at runtime. -## See Also +## Constraints & Edge Cases -- [Policies](/reference/policies) - Learn about policies and their structure -- [Rules](/reference/rules) - Learn about rules and how they work -- [Facts](/reference/facts) - Learn about facts and input data declarations -- [Expressions](/reference/index#expressions) - Learn about expressions and operators +- Same name in inner block shadows outer. Constraint validation failure aborts evaluation. diff --git a/src/content/docs/reference/namespaces.md b/src/content/docs/reference/namespaces.md index 7745815..6055f19 100644 --- a/src/content/docs/reference/namespaces.md +++ b/src/content/docs/reference/namespaces.md @@ -1,68 +1,51 @@ --- title: Namespaces -description: Namespaces are a way to organize your policies and shapes. +description: Namespace syntax and rules for organizing policies and shapes. --- -Namespaces are a way to organize your policies and shapes. These MUST be declared at the top of a `*.sentrie` file. +# Namespaces -Namespaces form a natural hierarchy using the `/` delimiter. - -A `namespace` is a container for related: - -- policies -- shapes -- child namespaces +Namespaces group policies and shapes and form the visibility boundary for unexported shapes. Each `*.sentrie` file has exactly one namespace and it must be the first statement. ## Syntax ```text -// policy.sentrie --- This is a namespace declaration -namespace fully/qualified/namespace/name +namespace fully/qualified/name ``` -Where `FQN` (Fully Qualified Name) is a slash-separated identifier: +FQN is slash-separated identifiers (e.g. `com/example/auth`, `mycompany/policies/security`). + +## Parameters + +| Element | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| FQN | identifier path | Yes | Slash-separated; valid identifiers only. | + +**Returns:** N/A (declaration). + +## Examples + +### Basic Usage ```text namespace com/example/auth -namespace com/example/billing -namespace com/company/finance/reports -namespace mycompany/policies/security -namespace mycompany/policies/privacy/gdpr +namespace com/example/billing/v2 ``` -In the above example, the namespace nesting looks like this: +### Advanced Usage -``` -com -|-- example -| |-- auth -| |-- billing -|-- company -| |-- finance -| |-- reports - -mycompany -|-- policies -| |-- security -| | |-- ... -| |-- privacy -| | |-- gdpr -| | |-- ... +```text +namespace mycompany/policies/privacy/gdpr ``` -## Rules +## Behavior & Constraints -- 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 +- Only comments may appear before the namespace declaration. +- One namespace per file. +- Namespace forms the visibility boundary: unexported shapes are visible only within the same namespace. +- Multiple root namespaces are allowed across a policy pack (different files). -## Best Practices +## Constraints & Edge Cases -- Use descriptive namespace names that reflect their purpose -- Group related policies and shapes in namespaces - refer to [Policies](/reference/policies/) and [Shapes](/reference/shapes/) -- Keep individual policies and shapes within a namespace focused on specific domains or features -- Consider using consistent naming patterns across your team +- Namespace names must be valid identifiers. +- Child “namespaces” are not separate declarations; hierarchy is by naming convention (e.g. `a/b/c`). diff --git a/src/content/docs/reference/policies.md b/src/content/docs/reference/policies.md index de6d2e0..b28ce76 100644 --- a/src/content/docs/reference/policies.md +++ b/src/content/docs/reference/policies.md @@ -1,471 +1,72 @@ --- title: Policies -description: Policies are the core organizational unit in Sentrie, containing rules, facts, and business logic. +description: "Policy syntax and allowed statements: facts, rules, let, use, export." --- -Policies are the fundamental building blocks of Sentrie policy packs. They encapsulate business rules, define input data structures, and export decision outcomes that can be consumed by applications or other policies. +# Policies -## Policy Structure +A policy is a named block inside a namespace. It contains facts (inputs), rules (decisions), optional `let` bindings and `use` statements, and at least one `export decision of` rule. -### Basic Requirements +## Syntax -- **Namespace**: Every policy must belong to a namespace -- **Rules**: Must declare one or more rules for decision logic -- **Export**: Must export at least one decision (rule outcome) - -### Core Components - -- **Facts**: Declare input data declarations with types and defaults with `fact` statements -- **Variables**: Intermediate calculations using `let` statements -- **Rules**: Decision logic with conditions and outcomes with `rule` statements -- **Use**: External TypeScript functions via `use` statements -- **Exports**: Rule outcomes for external consumption with `export` statements +```text +policy IDENT { + fact ... + let ... + use ... + rule ... + export decision of IDENT [ attach ... ] +} +``` -## Declaring Policies +## Parameters -### Basic Syntax +| Statement | Required | Description | +| :--- | :--- | :--- | +| `fact` | No | Inputs; must appear before rules if present. | +| `let` | No | Intermediate values; scoped to block. | +| `use` | No | Import TypeScript functions. | +| `rule` | Yes (≥1) | Decision logic. | +| `export decision of` | Yes (≥1) | Exposes a rule for execution or import. | -```sentrie -namespace com/example/domain +**Returns:** N/A (container). Evaluation returns the exported rule decision(s). -policy policyName { - // Policy body -} -``` +## Examples -### Complete Example +### Basic Usage ```sentrie -// user-access.sentrie namespace com/example/auth -shape User { - id!: string - role!: string - permissions!: list[string] -} - -shape Resource { - id!: string - type!: string - owner!: string -} - policy userAccess { - use { verifySignature, isBusinessHours } from "./auth-utils.ts" as auth - fact user: User as currentUser - fact resource: Resource as currentResource - fact context?: Context as ctx default { "environment": "production" } - - let isResourceOwner = user.id == resource.owner - let hasValidSignature = auth.verifySignature(user.id, resource.id) - let isWithinBusinessHours = auth.isBusinessHours() - - rule canRead = default false when (resource.type == "document") { - yield isResourceOwner and hasValidSignature - } - - rule canWrite = default false when (resource.type == "document") { - yield isResourceOwner and hasValidSignature and isWithinBusinessHours - } - - rule canAccess = canRead or canWrite - - export decision of canAccess -} -``` - -## Policy Components - -### Use Statements - -```sentrie --- Using TypeScript functions -use { function1, function2 } from "./utils.ts" as utils -use { validateEmail } from "./validation.ts" as validators -``` - -### Fact Declarations - -```sentrie --- Required facts (default behavior, must be provided) -fact user: User as currentUser -fact resource: Resource as currentResource - --- Optional facts (marked with ?, can have defaults) -fact context?: Context as ctx default { "key": "value" } -fact config?: Config as settings default { "environment": "production" } -``` - -:::note[Important] - -- Facts are **required by default** - must be provided during execution -- Use `?` to mark facts as **optional** - can be omitted -- Only **optional facts** (`?`) can have default values -- Facts are **always non-nullable** - null values are not allowed - ::: - -:::note -Read more on facts [here](/reference/facts). -::: - -### Variable Declarations - -```sentrie --- Simple calculations -let isAdmin = user.role == "admin" -let totalPrice = item.price * quantity - --- Complex logic -let canAccess = user.active and - (user.role == "admin" or user.permissions contains "read") -``` - -:::note[Important] - -- `let` declarations are **scoped to their immediate block** (`{}`) -- `let` declarations **cannot be exported** - only rules can be exported -- `let` declarations are **immutable** - once declared, their value cannot be changed -- `let` declarations are used for **intermediate calculations** within a policy or rule block - ::: - -:::note -Read more on let declarations [here](/reference/let). -::: - -### Rule Declarations - -```sentrie --- Basic rule -rule canRead = default false { - yield user.role == "admin" + rule allow = default false { yield user.role == "admin" } + export decision of allow } - --- Conditional rule -rule canWrite = default false when (user.active) { - yield user.role == "admin" and user.verified -} - --- Composite rule -rule canAccess = canRead or canWrite -``` - -:::note -Read more on rules [here](/reference/rules). -::: - -### Export Rule outcomes - -```sentrie --- Export rule outcome -export decision of canAccess - --- Export multiple outcomes -export decision of canRead -export decision of canWrite - --- Export with attachments -export decision of canAccess - attach reason as "Access granted" - attach level as user.role -export decision of canRead - attach permissions as user.permissions - attach timestamp as currentTime() -``` - -:::note -For a complete guide on exporting and importing rules, see [Exporting and Importing Rules](/reference/exporting-and-importing-rules). -::: - -### Import Rule outcomes of other policies - -```sentrie --- Import rule from another policy -rule externalRule = import decision of ruleName - from com/example/other/policy - with factName as expr - --- Example -rule userPermission = import decision of canAccess - from com/example/auth/userAccess - with user as currentUser - --- Accessing attachments from imported rules -rule authResult = import decision of canAccess - from com/example/auth/userAccess - with user as currentUser -let accessReason = authResult.reason -- Access the 'reason' attachment -let accessLevel = authResult.level -- Access the 'level' attachment -let userPermissions = authResult.permissions -- Access the 'permissions' attachment -``` - -:::note -For detailed information on importing rules, fact injection, and accessing attachments, see [Exporting and Importing Rules](/reference/exporting-and-importing-rules). -::: - -## Rule Attachments - -Rule attachments allow you to include additional metadata with exported rule outcomes. This metadata can be accessed when the rule is imported by other policies or executed via the HTTP API. - -### Exporting with Decision Attachments - -```sentrie --- Basic attachment syntax -export decision of ruleName (attach name as expression)* - --- Examples -export decision of canAccess - attach reason as "Access granted" - -export decision of canRead - attach permissions as user.permissions - attach timestamp as currentTime() - -export decision of canWrite - attach level as user.role - attach department as user.department -``` - -### Importing and Accessing Decision Attachments - -```sentrie --- Import rule with attachments -rule importedRule = import decision of ruleName - from com/example/policy - with factName as expr - --- Access attachments using field accessors -let attachmentValue = importedRule.attachmentName - --- Example -rule authResult = import decision of canAccess - from com/example/auth/userAccess - with user as currentUser -let accessReason = authResult.reason -- Access the 'reason' attachment -let accessLevel = authResult.level -- Access the 'level' attachment -let userPermissions = authResult.permissions -- Access the 'permissions' attachment ``` -### Practical Use Cases +### Advanced Usage ```sentrie --- Export with debugging information -export decision of canAccess attach debugInfo as "User verified" attach timestamp as currentTime() - --- Export with business context -export decision of canApprove attach approverLevel as user.role attach approvalLimit as user.maxAmount - --- Export with audit trail -export decision of canDelete attach auditReason as "Data retention policy" attach retentionDate as item.createdDate -``` - -## Best Practices for Attachments - -### Use Descriptive Names - -```sentrie --- Good: Clear, descriptive attachment names -export decision of canAccess - attach accessReason as "User has admin role" - attach accessLevel as user.role - --- Avoid: Generic or unclear names -export decision of canAccess - attach info as "ok" - attach data as user.role -``` - -### Keep Attachments Relevant - -```sentrie --- Good: Only include necessary metadata -export decision of canApprove - attach approverLevel as user.role - attach approvalLimit as user.maxAmount - --- Avoid: Including unnecessary or sensitive data -export decision of canApprove - attach approverLevel as user.role - attach userPassword as user.password -``` - -### Use Consistent Naming Conventions - -```sentrie --- Good: Consistent naming pattern -export decision of canRead - attach readReason as "User has read permission" - attach readLevel as user.role - -export decision of canWrite - attach writeReason as "User has write permission" - attach writeLevel as user.role - --- Good: Use meaningful prefixes -export decision of canAccess - attach accessReason as "Access granted" - attach accessLevel as user.role -``` - -### Handle Missing Attachments Gracefully - -```sentrie --- Check if attachment exists before accessing -rule authResult = import decision of canAccess - from com/example/auth/userAccess - with user as currentUser - -let reason = authResult.reason is defined ? authResult.reason : "No reason provided" -let level = authResult.level is defined ? authResult.level : "unknown" -``` - -### Use Attachments for Debugging - -```sentrie --- Include debugging information in development -export decision of canAccess - attach debugInfo as "User verified: " + user.id - attach timestamp as currentTime() - --- In production, you might remove or simplify debug attachments -export decision of canAccess - attach timestamp as currentTime() -``` - -### Document Attachment Usage - -```sentrie --- Document what attachments are available policy userAccess { - -- Exports canAccess with attachments: - -- - reason: Human-readable explanation - -- - level: User's access level - -- - timestamp: When decision was made - export decision of canAccess - attach reason as "Access granted" - attach level as user.role - attach timestamp as currentTime() -} -``` - -### Avoid Overusing Attachments - -```sentrie --- Good: Focused, essential attachments -export decision of canAccess - attach reason as "Access granted" - attach level as user.role - --- Avoid: Too many attachments that clutter the interface -export decision of canAccess - attach reason as "Access granted" - attach level as user.role - attach timestamp as currentTime() - attach debugInfo as "User verified" - attach sessionId as user.sessionId - attach requestId as request.id -``` - -### Use Proper Alignment - -```sentrie --- Good: Align attachments for readability -export decision of canAccess - attach reason as "Access granted" - attach level as user.role - attach timestamp as currentTime() - --- Good: Align import statements for readability -rule authResult = import decision of canAccess - from com/example/auth/userAccess - with user as currentUser - --- Avoid: Single line when it becomes too long -export decision of canAccess attach reason as "Access granted" attach level as user.role attach timestamp as currentTime() attach debugInfo as "User verified" -``` - -## Policy Interactions - -### Rule Composition - -```sentrie --- Combine multiple rules - -let isBusinessHours = ... - -rule canRead = ... -rule canWrite = ... -rule complexAccess = { yield canRead and canWrite and isBusinessHours } - --- Conditional composition -rule conditionalAccess = { yield user.role == "admin" ? canAccess : canRead } -``` - -### Cross-Policy Dependencies - -```sentrie --- Import from another policy -rule authResult = import decision of authenticate - from com/example/auth/login - with user as currentUser - --- Use imported result -rule canProceed = authResult and user.verified -``` - -:::note -Learn more about cross-policy dependencies, sandboxing, and best practices in [Exporting and Importing Rules](/reference/exporting-and-importing-rules). -::: - -## Best Practices - -### Clear Naming - -```sentrie --- Good: Descriptive policy and rule names -policy userAccessControl { - rule canReadDocument = default false { /* ... */ } - rule canWriteDocument = default false { /* ... */ } -} - --- Avoid: Generic names -policy policy1 { - rule rule1 = default false { /* ... */ } -} -``` - -### Logical Organization - -```sentrie --- Group related rules -policy documentAccess { - rule canRead = default false { /* read logic */ } - rule canWrite = default false { /* write logic */ } - rule canDelete = default false { /* delete logic */ } - - rule canAccess = canRead or canWrite or canDelete + use { sha256 } from @sentrie/hash + fact user: User as currentUser + fact context?: Context as ctx default {} + let adminRoles = ["admin", "super_admin"] + rule canRead = default false when user.role is defined { + yield user.role in adminRoles + } + export decision of canRead } ``` -### Error Handling +## Behavior & Constraints -```sentrie --- Provide sensible defaults -rule canAccess = default false when (user is defined and resource is defined) { - yield user.active and user.verified -} -``` +- Facts must be declared before rules (only facts or comments may precede facts). +- At least one rule must be exported for the policy to be executable or importable. +- Rules in the same policy may reference each other without import. -### Documentation +## Constraints & Edge Cases -```sentrie --- Document complex logic -policy complexBusinessLogic { - -- This rule implements the company's access control policy - -- Users must be active, verified, and have appropriate role - rule canAccess = default false { - yield user.active and user.verified and user.role in ["admin", "manager"] - } -} -``` +- Policy name is an identifier. Same namespace may contain multiple policies. +- Exported rule names are the target for CLI/API and for `import decision of` from other policies. diff --git a/src/content/docs/reference/precedence.md b/src/content/docs/reference/precedence.md index 0763a91..20886f0 100644 --- a/src/content/docs/reference/precedence.md +++ b/src/content/docs/reference/precedence.md @@ -1,112 +1,54 @@ --- title: Precedence -description: Operator precedence determines the order of operations in expressions. +description: Operator precedence (highest to lowest) for expressions. --- -Operator precedence defines which operations are performed first when multiple operators appear in an expression. Higher precedence operations are evaluated before lower precedence ones. +# Precedence -## Precedence Table (highest to lowest) +Operators are evaluated in order of precedence (highest first). Same precedence is left-to-right unless stated otherwise. Use parentheses to override. -| Precedence | Operators | Description | -| ---------- | ------------------------------------------------- | ----------------------------------------------------------- | -| 1 | `()`, `[]`, `.` | Primary expressions (literals, identifiers, function calls) | -| 2 | `not`, `!`, `+`, `-` | Unary operators | -| 3 | `*`, `/`, `%` | Multiplicative arithmetic | -| 4 | `+`, `-` | Additive arithmetic | -| 5 | `<`, `<=`, `>`, `>=`, `in`, `matches`, `contains` | Comparison operators | -| 6 | `==`, `!=`, `is`, `is not` | Equality operators | -| 7 | `and` | Logical AND | -| 8 | `xor` | Logical XOR | -| 9 | `or` | Logical OR | -| 10 | `? :` | Ternary conditional | +## Syntax -## Examples - -### Arithmetic Precedence - -```sentrie -let result: number = 2 + 3 * 4 -- Result: 14 (not 20) -let calculation: number = 10 / 2 + 3 -- Result: 8.0 (not 1.25) -``` - -### Comparison vs Arithmetic - -```sentrie -let is_valid: bool = 5 + 3 > 7 -- Result: true -let check: bool = 10 * 2 == 20 -- Result: true -``` - -### Logical Precedence - -```sentrie -let complex: bool = true and false or true -- Result: true -let mixed: bool = 5 > 3 and 2 < 4 -- Result: true -``` - -### Ternary Precedence +Expressions combine operators; precedence determines binding. Ternary `? :` is right-associative. -```sentrie -let value: number = 5 > 3 ? 10 : 20 -- Result: 10 -let nested: string = true ? (false ? "A" : "B") : "C" -- Result: "B" -``` +## Parameters -## Using Parentheses +| Precedence | Operators | Description | +| :--- | :--- | :--- | +| 1 | `()`, `[]`, `.` | Primary (literals, identifiers, calls, indexing) | +| 2 | `not`, `!`, unary `+`, `-` | Unary | +| 3 | `*`, `/`, `%` | Multiplicative | +| 4 | `+`, `-` | Additive | +| 5 | `<`, `<=`, `>`, `>=`, `in`, `matches`, `contains` | Comparison | +| 6 | `==`, `!=`, `is`, `is not` | Equality | +| 7 | `and` | Logical AND | +| 8 | `xor` | Logical XOR | +| 9 | `or` | Logical OR | +| 10 | `? :` | Ternary conditional | -### Override Precedence +**Returns:** N/A (ordering rule). Result type is that of the top-level expression. -```sentrie --- Without parentheses (follows precedence) -let result1: number = 2 + 3 * 4 -- Result: 14 - --- With parentheses (override precedence) -let result2: number = (2 + 3) * 4 -- Result: 20 -``` +## Examples -### Complex Expressions +### Basic Usage ```sentrie --- Clear grouping for readability -let access: bool = (user.age >= 18 and user.verified) or - (user.role == "admin" and user.active) - --- Mixed operations -let calculation: number = (10 + 5) * (3 - 1) / 2 -- Result: 15.0 - --- Same precedence is evaluated from left to right -let calculation: number = (10 + 5) * (5 - 2) / 2 -- Result: 22.5 (15 * 3 / 2) - +let result: number = 2 + 3 * 4 -- 14 +let valid: bool = 5 + 3 > 7 -- true +let complex: bool = true and false or true -- true ``` -## Best Practices - -### Use Parentheses for Clarity +### Advanced Usage ```sentrie --- Good: Clear intent -let result: bool = (a and b) or (c and d) - --- Avoid: Relying on precedence knowledge -let result: bool = a and b or c and d +let value: number = 5 > 3 ? 10 : 20 -- 10 +let nested: string = true ? (false ? "A" : "B") : "C" -- "B" ``` -### Group Related Operations - -```sentrie --- Good: Logical grouping -let price: number = (base_price + tax) * discount_rate +## Behavior & Constraints --- Good: Comparison grouping -let valid: bool = (age >= 18) and (income > 50000) -``` +- Parentheses override precedence. Same level: left to right except ternary (right-associative). -### Break Complex Expressions +## Constraints & Edge Cases -```sentrie --- Good: Step-by-step calculation -let temp1: number = base_price + tax -let temp2: number = temp1 * discount_rate -let final_price: number = temp2 - shipping - --- Avoid: One complex expression -let final_price: number = (base_price + tax) * discount_rate - shipping -``` +- Use parentheses when in doubt to make intent explicit. diff --git a/src/content/docs/reference/rules.md b/src/content/docs/reference/rules.md index 5ae8955..765928f 100644 --- a/src/content/docs/reference/rules.md +++ b/src/content/docs/reference/rules.md @@ -1,200 +1,54 @@ --- title: Rules -description: Rules are a way to organize your rules and facts. +description: Rule syntax, evaluation (when/default/body), and outcome (trinary or value). --- -Rules form the core principle of policies in Sentrie. A policy is essentially a collection of rules and their outcome decisions that work together to make business decisions. Understanding how rules function is fundamental to building effective Sentrie policies. +# Rules -## The Foundation of Policies +A rule defines a decision: an optional `when` condition, an optional `default`, and a body that must contain `yield`. If `when` is truthy the body is evaluated; otherwise the `default` (or `unknown`) is used. -Since rules are the building blocks that define business logic, every Sentrie policy must contain at least one exported rule. +## Syntax -A rule must be exported to be executed by the runtime or to be imported and used in other policies. This export mechanism enables modular policy design where complex business logic can be broken down into reusable component rules. - -## Rule Structure and Evaluation - -Rules consist of three essential components: - -- a **body** (required) -- a **default** (optional) -- a **when predicate** (optional) - -The `when` predicate acts as a gatekeeper, determining whether the rule's `body` should be evaluated or if the `default` should be used instead. - -When the `when` condition evaluates to [truthy](/reference/trinary), the rule's body is executed to produce the final decision. If the `when` condition is not [truthy](/reference/trinary), the rule falls back to its `default` value. A rule can have one of the three possible outcomes any rule can have: `TRUE`, `FALSE`, or `UNKNOWN`. - -```typescript -is_truthy(when) ? body : default -``` - -:::note[Remember] -If no `default` is provided and `when` is not [truthy](/reference/trinary), the rule's outcome falls back to `unknown`. -::: - -### Example Rule - -```sentrie -namespace com/example/auth - -policy user { - rule isAdmin = default false when user.role is defined { - yield user.role is "admin" or user.role is "super_admin" - } - export decision of isAdmin attach role as user.role -} -``` - -:::note[Remember] - -- `when` and `default` are optional -- `when` is `true` by default -- `default` is `unknown` by default -- If `when` is not [truthy](/reference/trinary) and no `default` is provided, the rule's outcome is `unknown` - -::: - -## Sharing Rules Across Policies - -Rules can be exported as decisions, making them available for consumption in other policies. This enables powerful policy composition where complex business decisions can be built by combining simpler, focused rules. When exporting a rule as a decision, you can attach named data that callers can access and consume. - -Exported rules can include named attachments that provide additional context or metadata about the decision. These attachment values must be proactively consumed by the calling policy, ensuring that important information isn't lost in the decision-making process. - -### Example - -```sentrie -// auth/auth.sentrie -namespace com/example/auth - -policy base { - fact user!:User as u -- alias for the user fact - - rule isAdmin = default false when user.role is defined { - yield user.role is "admin" or user.role is "super_admin" - } - export decision of isAdmin attach role as user.role -} -``` - -```sentrie -// user/user.sentrie -namespace com/example/user - -policy user_access { - fact user!:User as user - - rule isAdmin = import decision of isAdmin from com/example/auth/base with u as user -} -``` - -## Importing and Sandboxing - -Rules can be imported from other policies, but this process involves careful isolation to maintain policy boundaries. When importing a rule decision, you must inject the necessary facts that the imported rule needs to evaluate. Crucially, imported rules are evaluated in their own sandboxed environment, which means they cannot affect or modify the context of the calling policy. - -This sandboxing only applies to rules imported from other policies. Rules within the same policy can reference each other directly without import restrictions, allowing for efficient internal policy organization while maintaining security boundaries between different policy modules. - -## Practical Examples - -Let's explore how these concepts work in practice through various rule patterns and scenarios. - -### Basic Rule Patterns - -The simplest rule pattern involves explicit boolean outcomes. Here's a rule that always allows login: - -```sentrie -policy auth { - rule allow_login = { - yield true - } - export decision of allow_login -} +```text +rule IDENT = [ default expr ] [ when expr ] { stmt* yield expr } ``` -More sophisticated rules use the `when` predicate to conditionally evaluate expensive operations. Consider a feature flag rule that defaults to false for non-beta users, avoiding unnecessary computation: +## Parameters -```sentrie -policy feature { - rule enable_feature = default false when user.is_beta == false { - yield someExpensiveCheck() - } - export decision of enable_feature -} -``` +| Part | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| `default expr` | expression | No | Used when `when` is not truthy; default is `unknown` if omitted. | +| `when expr` | trinary | No | Guard; if not truthy, rule outcome is `default`. Default when omitted is true. | +| body | block | Yes | Must contain exactly one `yield expr`. | -When a rule's `when` condition is false and no `default` is provided, the outcome becomes UNKNOWN. This pattern is useful for gated functionality: +**Returns:** The `yield` expression value when body runs, else `default` (or `unknown`). Type is trinary or any value type. -```sentrie -policy gated { - rule gated_rule = when system.ready == false { - yield true - } - export decision of gated_rule -} -``` +## Examples -Rules can also leverage truthiness evaluation for cleaner code. This session validation rule returns true if the session ID is non-empty: +### Basic Usage ```sentrie -policy auth { - rule has_session = default false when true { - yield session.id // Truthy if non-empty string - } - export decision of has_session -} +rule allow = default false { yield true } +rule isAdmin = default false when user.role is defined { yield user.role == "admin" } ``` -### Policy Composition and Attachments - -Rules become powerful when composed across policies. Here's how to export a decision with additional context through attachments: - -```sentrie -// shapes.sentrie -namespace com/example/auth - -shape Account { - balance!: number -} - -shape Invoice { - total!: number -} -``` +### Advanced Usage ```sentrie -// billing.sentrie -namespace com/example/billing - -policy billing { - fact account!: Account as account - fact invoice!: Invoice as invoice - - let reason = account.balance >= invoice.total ? "insufficient_funds" : "sufficient_funds" - let balance = account.balance - - rule payment_ok = default false { - yield reason - } - - export decision of payment_ok - attach reason as reason - attach balance as balance +rule getPrice = default 0 when product.price is defined { + let base = product.price + let discount = user.isPremium ? 0.1 : 0.05 + yield base * (1 - discount) } ``` -When importing this decision, you inject the necessary facts and receive the decision in a sandboxed environment: +## Behavior & Constraints -```sentrie -policy shipping { - fact account!: Account as account - fact invoice!: Invoice as invoice - - rule payment_reason_consumed = import decision of payment_ok from com/example/billing - with account as account - with invoice as invoice +- Evaluation: `is_truthy(when) ? body_result : default`. Truthy follows [trinary](/reference/trinary) semantics. +- Body must yield exactly once when evaluated. Only the chosen branch (body or default) is evaluated. +- Rules in the same policy can reference other rules by name. Exported rules can be imported elsewhere. - export decision of payment_reason_consumed - attach reason as payment_ok.reason -- access the reason attachment - attach balance as payment_ok.balance -- access the balance attachment -} -``` +## Constraints & Edge Cases -This pattern enables rich policy composition where decisions carry not just boolean outcomes but also contextual information that can inform downstream authorization logic. +- If no `default` and `when` is not truthy, outcome is `unknown`. +- Recursion in rule evaluation is bounded by the language (no unbounded recursion). diff --git a/src/content/docs/reference/security-and-permissions.md b/src/content/docs/reference/security-and-permissions.md index c7a50e4..13ec894 100644 --- a/src/content/docs/reference/security-and-permissions.md +++ b/src/content/docs/reference/security-and-permissions.md @@ -1,31 +1,54 @@ --- -title: Permissions -description: Permissions are the security permissions and capabilities that can be used in a policy pack. +title: Security and Permissions +description: "Policy pack permissions: filesystem, network, environment." --- -Permissions are the security permissions and capabilities that can be used in a policy pack. +# Security and Permissions -## Purpose +Permissions define what a policy pack can access: filesystem (read), network hosts, and environment variables. They are set in `sentrie.pack.toml` under `[permissions]`. Default is pack-root filesystem only; no network; no env. -Permissions are used to define the security permissions and capabilities that can be used in a policy pack. This includes access to the filesystem and network. Permissions are defined in the `sentrie.pack.toml` file in the `permissions` section. +## Syntax + +```text +[permissions] +fs_read = ["/path1", "/path2"] +net = ["host1.com", "host2.com"] +env = ["VAR1", "VAR2"] +``` -By default, policies and it's modules have access to +## Parameters -- the filesystem rooted at the policy pack root -- no network access. -- no access to the environment variables. +| Key | Type | Description | +| :--- | :--- | :--- | +| `fs_read` | list[string] | Paths (or prefixes) that the pack can read. Default: pack root. | +| `net` | list[string] | Hosts (or patterns) allowed for network access. Default: none. | +| `env` | list[string] | Environment variable names exposed to the pack. Default: none. | -## Syntax +**Returns:** N/A (configuration). Violations (e.g. reading outside fs_read) cause runtime failure. -```text +## Examples + +### Basic Usage + +```toml +[permissions] +fs_read = ["."] +``` + +### Advanced Usage + +```toml [permissions] fs_read = ["/etc/passwd"] net = ["example.com"] env = ["ORG_DSN", "REDIS_PASSWORD"] ``` -In the above example, the policy pack has +## Behavior & Constraints + +- By default: filesystem access is limited to the policy pack root; no network; no environment variables. +- Explicit entries grant access only to listed paths/hosts/vars. Modules run with the same permissions as the pack. + +## Constraints & Edge Cases -- read access to the `/etc/passwd` file -- network access to `http://example.com` -- access to the `ORG_DSN` and `REDIS_PASSWORD` environment variables. +- Invalid or missing paths/hosts may be rejected at load or runtime. Restrict permissions to the minimum required. diff --git a/src/content/docs/reference/shapes.md b/src/content/docs/reference/shapes.md index e881b81..6061c3d 100644 --- a/src/content/docs/reference/shapes.md +++ b/src/content/docs/reference/shapes.md @@ -1,414 +1,63 @@ --- title: Shapes -description: Shapes are a way to define type aliases and data models in sentrie. +description: "Shape syntax: data models, field modifiers, composition, and type aliases." --- -Shapes are Sentrie's primary mechanism for defining custom data models and type aliases. They provide a powerful way to create reusable, validated types that can be composed together to build complex data models. +# Shapes -Shapes help in providing clear contracts for data, thus providing a built in validation layer for data that flows through your policies. +Shapes define structured data (fields) or type aliases (base type + constraints). They are used for facts and `let` bindings. Optional fields use `?`; required non-null use `!`. Composition uses `with Base`. -## What are Shapes? +## Syntax -Shapes serve two main purposes in Sentrie: - -- **Data Models**: Define structured objects with named fields and constraints -- **Type Aliases**: Create custom names for existing types with additional constraints - -## Defining Shapes - -### Type Aliases - -Shapes can be used to create type aliases for built-in types with additional constraints: - -```sentrie -shape ID string @uuid() -shape Email string @email() -shape Username string @minlength(3) @maxlength(20) @alphanumeric() -shape UserAge number @min(13) @max(120) -``` - -This creates constrained types that can be used throughout your policies: - -```sentrie -let id: ID = "123e4567-e89b-12d3-a456-426614174000" -- Valid -let email: Email = "alice@example.com" -- Valid -let username: Username = "alice123" -- Valid -let age: UserAge = 25 -- Valid -let invalid_username: Username = "ab" -- Validation error: too short -``` - -### Simple Data Models - -The most common use of shapes is to define structured data models: - -```sentrie -shape User { - name: string - age: number - email: string -} -``` - -This creates a `User` shape with three fields. You can then use this shape to create and validate user data: - -```sentrie -let user: User = { - name: "Alice", - age: 28, - email: "alice@example.com" -} -``` - -#### Field Nullability and Optionality - -Shapes support different field requirements using special markers: - -- **Required fields** (default): Field must be present and can be null -- **Required non-null fields** (`!`): Field must be present and cannot be null -- **Optional fields** (`?`): Field may be omitted entirely - -```sentrie -shape User { - name!: string -- Required, cannot be null - age: number -- Required, can be null - email?: string -- Optional, can be omitted - phone!?: string -- Optional, but if present cannot be null -} -``` - -Examples of valid and invalid usage: - -```sentrie --- Valid: all required fields present, optional field omitted -let user1: User = { - name: "Alice", - age: 25 -} - --- Valid: all fields present, including optional -let user2: User = { - name: "Bob", - age: 30, - email: "bob@example.com" -} - --- Valid: required field can be null -let user3: User = { - name: "Charlie", - age: null -} - --- Invalid: required non-null field is null -let user4: User = { - name: null, -- Error: name cannot be null - age: 25 -} - --- Invalid: required field is missing -let user5: User = { - age: 25 -- Error: name is required +**Data model:** +```text +shape Name { + field!: type + field?: type + field: type } ``` -:::note[Checking Optional Fields] -You can check if optional fields are defined using the `is defined` operator: - -```sentrie -let user: User = { - name: "Alice", - age: 25, - email: "alice@example.com" -} +**Alias:** `shape Name baseType @constraint` --- You can also use this in boolean expressions -let has_phone: bool = user.phone is defined -let contact_info: string = user.phone is defined ? user.phone : "No phone available" +**Composition:** `shape Child with Base { ... }` --- Check if optional fields exist using ternary operator -let phone_message: string = user.phone is defined ? "Phone: " + user.phone : "No phone number provided" -``` +## Parameters -::: +| Modifier | Description | +| :--- | :--- | +| `!` | Required, non-null. | +| `?` | Optional; may be omitted. | +| none | Required, may be null. | +| `!?` | Optional; if present, non-null. | -## Shape Composition +**Returns:** N/A (type definition). Values are validated when assigned or passed as facts. -Shapes can be composed from other shapes using the `with` keyword, allowing you to build complex data models. +## Examples -### Basic Composition +### Basic Usage ```sentrie shape User { - name: string + name!: string age: number -} - -shape AdminUser with User { - permissions: list[string] - department: string -} -``` - -The `AdminUser` shape includes all fields from `User` and adds additional fields: - -```sentrie -let admin: AdminUser = { - name: "John", - age: 30, - permissions: ["read", "write", "delete"], - department: "Engineering" -} -``` - -### Composition Rules - -- **Composition**: Composed shapes include all fields from base shapes -- **No Circular Dependencies**: Shapes cannot reference themselves directly or indirectly -- **No Duplicate Fields**: Shapes cannot have duplicate fields directly or indirectly -- **Type Safety**: Values must match the exact shape they're assigned to -- **Data Model Requirement**: Shapes cannot be composed with alias shapes - -```sentrie --- This will cause a validation error -let user: User = { - name: "John", - age: 30, - permissions: ["read", "write"] -- User doesn't have permissions field -} -``` - -## Applying Constraints - -Shape properties can have constraints applied to them, providing runtime validation: - -```sentrie -shape User { - name: string @minlength(2) @maxlength(50) @not_empty() - age: number @min(0) @max(150) - email: string @email() @not_empty() - score: number @range(0.0, 100.0) + email?: string } ``` -When creating instances of this shape, all constraints are validated: +### Advanced Usage ```sentrie -let user: User = { - name: "Alice", -- Valid: meets length requirements - age: 25, -- Valid: within range - email: "alice@test.com", -- Valid: proper email format - score: 85.5 -- Valid: within range -} - --- This would fail validation -let invalid_user: User = { - name: "A", -- Too short - age: -5, -- Below minimum - email: "invalid-email", -- Not a valid email - score: 150.0 -- Above maximum -} -``` - -## Nested Data Models - -Shapes excel at modeling complex, nested data models with proper nullability handling: - -```sentrie -shape Address { - street!: string @not_empty() - city!: string @not_empty() - state!: string @length(2) @uppercase() - zip!: string @regexp("^[0-9]{5}(-[0-9]{4})?$") -} - -shape ContactInfo { - email!: string @email() - phone?: string @regexp("^\\+?[1-9]\\d{1,14}$") -- Optional phone - address?: Address -- Optional address -} - -shape User { - name!: string @minlength(2) @maxlength(100) - age: number @min(13) @max(120) -- Can be null if unknown - contact!: ContactInfo - preferences?: map[string] -- Optional preferences -} -``` - -Usage examples showing different nullability scenarios: - -```sentrie --- Complete user with all fields -let user1: User = { - name: "John Doe", - age: 30, - contact: { - email: "john@example.com", - phone: "+1234567890", - address: { - street: "123 Main St", - city: "Anytown", - state: "CA", - zip: "12345" - } - }, - preferences: { - "theme": "dark", - "notifications": "enabled" - } -} - --- User with minimal required fields (optional fields omitted) -let user2: User = { - name: "Jane Smith", - age: null, -- Age unknown - contact: { - email: "jane@example.com" - -- phone and address are optional, so omitted - } - -- preferences are optional, so omitted -} - --- User with some optional fields -let user3: User = { - name: "Bob Wilson", - age: 25, - contact: { - email: "bob@example.com", - phone: "+1987654321" - -- address omitted (optional) - }, - preferences: { - "theme": "light" - } -} -``` - -## Shape Management - -### Local Availability - -Shapes are automatically available within their defining namespace: - -```sentrie -namespace com/example/users - -shape User { - name: string - age: number -} - -policy example { - -- User shape is available here - let user: User = { name: "Alice", age: 25 } -} -``` - -### Exporting Shapes - -To make shapes available to other namespaces, use the `export` keyword: - -```sentrie -// auth/auth.sentrie -namespace com/example/auth - -shape User { - id: string @uuid() - username: string @minlength(3) - email: string @email() -} - -export shape User -``` - -### Importing Shapes - -Other namespaces can import and use exported shapes by using the fully qualified name: - -```sentrie -// billing/billing.sentrie -namespace com/example/billing - -policy process_payment { - -- Import the User shape using the fully qualified name from auth namespace - shape User with com/example/auth/User { - billing_address: string - } - - let user: User = { - id: "123e4567-e89b-12d3-a456-426614174000", - username: "alice", - email: "alice@example.com", - billing_address: "456 Oak Ave" - } -} -``` - -### Policy Local Shapes - -Shapes defined in a policy are only available within that policy and **take precedence** over namespace shapes: - -```sentrie -// billing.sentrie -namespace com/example/billing - -shape User { - id!: string @uuid() - email!: string @email() -} - -policy process_payment_1 { - -- Policy-local shape (most specific) - shape User { - id!: string @uuid() - billing_address!: string - } - - -- This resolves to the policy-local `User` shape - let user: User = { - id: "123e4567-e89b-12d3-a456-426614174000", - billing_address: "123 Main St" - } - - ... -} - -policy process_payment_2 { - -- This resolves to the namespace `User` shape - let user: User = { - id: "123e4567-e89b-12d3-a456-426614174000", - billing_address: "123 Main St" - } - - ... -} +shape Base { id!: string } +shape Extended with Base { role!: string } +shape ID string @uuid() ``` -## Best Practices - -### Naming Conventions - -- Use PascalCase for shape names: `UserProfile`, `PaymentInfo` -- Use descriptive names that clearly indicate the shape's purpose -- Avoid generic names like `Data` or `Info` unless they're truly generic - -### Organization +## Behavior & Constraints -- Group related shapes in the same namespace -- Export only shapes that need to be used by other namespaces -- Use composition to avoid duplicating common fields +- Composed shape includes all fields of Base plus its own. Circular composition is not allowed. +- Optional fields: use `is defined` to check before use. Exported shapes are visible across namespaces. -### Best Practices +## Constraints & Edge Cases -- Apply appropriate constraints to shape fields -- Use meaningful constraint messages for better error reporting -- Use alias shapes for better readability - -```sentrie -shape Category string @one_of("electronics", "clothing", "books", "home") -shape Product { - name!: string @minlength(1) @maxlength(100) @not_empty() - price!: number @positive() @range(0.01, 999999.99) - category!: Category - in_stock: bool -} -``` +- Unexported shapes are namespace-local. Field order in literals does not affect type checking. diff --git a/src/content/docs/reference/trinary.md b/src/content/docs/reference/trinary.md index 04c582d..a9392aa 100644 --- a/src/content/docs/reference/trinary.md +++ b/src/content/docs/reference/trinary.md @@ -1,233 +1,63 @@ --- title: Trinary Values -description: Trinary values are a type of value that can be true, false, or unknown. +description: "Three-valued logic: true, false, unknown; truthiness and Kleene tables." --- -Sentrie uses **trinary logic** (also known as three-valued logic), which extends traditional boolean logic with a third value: `unknown`. This is essential for handling cases where information may be incomplete or unavailable. +# Trinary Values -### Trinary Values +Sentrie uses trinary logic: `true`, `false`, and `unknown`. Unknown represents indeterminate truth (e.g. undefined field access). Logical operators follow Kleene's three-valued logic. -Sentrie supports three trinary values: +## Syntax -- **`true`** - The condition is definitely true -- **`false`** - The condition is definitely false -- **`unknown`** - The condition's truth value cannot be determined +Literals: `true` | `false` | `unknown` -### Kleene Truth Tables +Logical: `and` | `or` | `xor` | `not` | `!` -Sentrie implements **Kleene's three-valued logic**, which provides a consistent way to handle `unknown` values in logical operations. +**Returns:** Trinary. For `when` and conditionals: only `true` is truthy; `false` and `unknown` are not. -#### Logical AND (`and`) +## Parameters -The `and` operator follows Kleene's AND truth table: +Truthiness: only `true` is truthy. `false` and `unknown` are non-truthy (e.g. for `when`, ternary, Elvis). -| **AND** | **true** | **false** | **unknown** | -| ----------- | --------- | --------- | ----------- | -| **true** | `true` | `false` | `unknown` | -| **false** | `false` | `false` | `false` | -| **unknown** | `unknown` | `false` | `unknown` | +## Examples -**Key behaviors:** - -- `true and x` = `x` (true is the identity element) -- `false and x` = `false` (false dominates) -- `unknown and true` = `unknown` (cannot determine if both are true) -- `unknown and false` = `false` (false dominates) -- `unknown and unknown` = `unknown` (cannot determine) - -#### Logical OR (`or`) - -The `or` operator follows Kleene's OR truth table: - -| **OR** | **true** | **false** | **unknown** | -| ----------- | -------- | --------- | ----------- | -| **true** | `true` | `true` | `true` | -| **false** | `true` | `false` | `unknown` | -| **unknown** | `true` | `unknown` | `unknown` | - -**Key behaviors:** - -- `true or x` = `true` (true dominates) -- `false or x` = `x` (false is the identity element) -- `unknown or true` = `true` (true dominates) -- `unknown or false` = `unknown` (cannot determine if either is true) -- `unknown or unknown` = `unknown` (cannot determine) - -#### Logical NOT (`not` or `!`) - -The `not` operator follows this truth table: - -| **Input** | **Output** | -| ----------- | ---------- | -| **true** | `false` | -| **false** | `true` | -| **unknown** | `unknown` | - -**Key behavior:** - -- `not unknown` = `unknown` (cannot determine the opposite of unknown) - -### Trinary Logic Examples - -#### Basic Trinary Operations - -```sentrie --- Unknown from undefined field access -let value = user.nonexistent.field -let result = value == "test" -- unknown (operation on unknown) - --- AND with unknown -let a = true -let b = unknown -let result1 = a and b -- unknown - -let c = false -let d = unknown -let result2 = c and d -- false (false dominates) - --- OR with unknown -let e = true -let f = unknown -let result3 = e or f -- true (true dominates) - -let g = false -let h = unknown -let result4 = g or h -- unknown -``` - -#### Practical Use Cases +### Basic Usage ```sentrie -shape User { - name!: string - email?: string - age?: number -} - -fact user: User - --- Check if user has email (handles unknown gracefully) -let has_email: trinary = user.email is defined and user.email is not empty --- Result: true if email exists and is not empty --- false if email is defined but empty --- unknown if email is not defined - --- Age verification with unknown handling -let can_vote: trinary = user.age is defined ? (user.age >= 18) : unknown --- Result: true if age >= 18 --- false if age < 18 --- unknown if age is not defined - --- Complex logic with unknown propagation -let is_verified: trinary = user.email is defined and - user.email matches "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" --- Result: true if email is defined and valid --- false if email is defined but invalid --- unknown if email is not defined +let a = true and unknown -- unknown +let b = false or unknown -- unknown +let c = not unknown -- unknown ``` -#### Handling Unknown in Rules +### Kleene AND -```sentrie -shape Account { - username!: string - email?: string - verified: bool -} - -fact account: Account - --- Rule that handles unknown gracefully -rule can_access = default false when account.email is defined { - let email_valid = account.email matches "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" - yield account.verified and email_valid -} - --- If email is not defined, the 'when' clause prevents evaluation --- If email is defined, the rule evaluates normally -``` - -#### Unknown Propagation - -```sentrie --- Unknown propagates through operations -let a = user.nonexistent.field -- undefined -let b = a + 1 -- undefined (operation on undefined) -let c = b > 10 -- undefined (comparison with undefined) -let d = c and true -- unknown (AND with undefined) -let e = d or false -- unknown (OR with unknown) -``` +| AND | true | false | unknown | +| --- | --- | --- | --- | +| true | true | false | unknown | +| false | false | false | false | +| unknown | unknown | false | unknown | -## Non-Trinary Value Interpretation +### Kleene OR -Sentrie can work with various data types, and when a value that isn't explicitly `true`, `false`, or `unknown` is used in a context that requires a `trinary` value, the system infers the `trinary` value based on the following rules: +| OR | true | false | unknown | +| --- | --- | --- | --- | +| true | true | true | true | +| false | true | false | unknown | +| unknown | true | unknown | unknown | -- `null` / `undefined` → `unknown` -- Numeric primitives (`int`, `float`, `uint`, etc.) evaluate to `false` when zero, `true` otherwise. -- `string` values are checked for textual keywords first (see table below), otherwise `true` when they are non-empty. -- Structs, channels, functions, and any other non-`nil` value evaluate to `true` +### NOT -### String coercion +| Input | Output | +| --- | --- | +| true | false | +| false | true | +| unknown | unknown | -Before falling back to the "non-empty string" rule, strings are normalized to lowercase and matched against the following keywords: +## Behavior & Constraints -| Input | Result | -| ------------------------------------------------------------ | --------- | -| `"true"`, `"1"`, `"t"` | `true` | -| `"false"`, `"0"`, `"f"` | `false` | -| `"unknown"`, `"-1"`, `"n"`, `"nil"`, `"null"`, `"undefined"` | `unknown` | -| Any other non-empty string | `true` | -| Empty string (`""`) | `false` | +- Undefined field access yields unknown; operations on unknown propagate unknown per Kleene tables. +- Rule `when` and ternary/Elvis use truthiness: only `true` is truthy. -### Collections +## Constraints & Edge Cases -For collections like `lists`, `maps`, `slices`, and `arrays`, truthiness depends on whether they contain elements. Empty collections are `false`; non-empty collections are `true`. - -## Trinary vs Boolean - -Sentrie does not distinguish between trinary and boolean values: - -- `true` (bool) → `true` (trinary) -- `false` (bool) → `false` (trinary) - -:::note -A `bool` value is a special case of a `trinary` value which can never be `unknown`. -::: - -## Best Practices for Trinary Logic - -1. **Check for definedness before operations:** - -```sentrie --- Good: Check if value is defined first -let result = user.email is defined ? str.length(user.email) > 0 : false - --- Avoid: Operations on potentially undefined values -let result = str.length(user.email) > 0 -- unknown if email is undefined -``` - -2. **Use `is defined` to handle unknown:** - -```sentrie --- Explicitly handle unknown cases -let can_proceed = user.age is defined and user.age >= 18 -``` - -3. **Understand unknown propagation:** - -```sentrie --- Unknown propagates through all operations -let value = user.missing.field -- unknown -let result = value + 1 -- unknown -let comparison = result > 10 -- unknown -``` - -4. **Use default values in rules:** - -```sentrie --- Provide defaults for unknown cases -rule can_access = default false when user.role is defined { - yield user.role == "admin" -} --- Returns false if role is not defined (unknown case) -``` +- `unknown` in a `when` causes the rule to use its default (or `unknown` outcome). `not unknown` is `unknown`. diff --git a/src/content/docs/reference/types-and-values.md b/src/content/docs/reference/types-and-values.md index 0e9a8f6..72936a5 100644 --- a/src/content/docs/reference/types-and-values.md +++ b/src/content/docs/reference/types-and-values.md @@ -1,114 +1,65 @@ --- title: Types and Values -description: Sentrie has a comprehensive type system with primitive types, collections, and user-defined shapes. +description: Built-in primitive and collection types and type declarations. --- -Sentrie provides a robust type system that includes primitive types, collection types, and [user-defined shapes](/reference/shapes/). The type system is enhanced by a powerful [constraint system](/reference/constraints) that allows you to validate values against specific rules. +# Types and Values -## Primitive Types +Sentrie provides primitive types (`number`, `string`, `trinary`, `bool`, `document`) and collection types (`list[T]`, `map[T]`, `record[T1,T2,...]`). User-defined shapes extend these. Types can be used in `let`, `fact`, and shape fields. -Sentrie supports five fundamental primitive types: +## Syntax -| Type | Description | -| ---------- | --------------------------------------------------------------- | -| `number` | Numeric values | -| `string` | Text strings | -| `trinary` | Trinary values (`true` / `false` / `unknown`) | -| `bool` | Boolean values (`true` / `false`) - a special case of `trinary` | -| `document` | Arbitrary JSON-like objects | +**Primitives:** `number` | `string` | `trinary` | `bool` | `document` -### Declaring a Primitive Type +**Collections:** `list[T]` | `map[T]` | `record[T1, T2, ...]` -```text -let u: number = 50 -``` - -```text -let u: number = 50.5 -``` - -```text -let u: string = "hello" -``` - -```text -let u: bool = true -``` - -```text -let u: document = { "name": "John", "age": 30 } -``` - -## Collection Types - -Collections allow you to work with groups of related values: - -| Type | Description | -| --------------------- | ------------------------------------------- | -| `list[T]` | Lists of type `T` | -| `map[T]` | Maps with `string` keys and type `T` values | -| `record[T1, T2, ...]` | Tuples with specific types | - -### Declaring a Collection Type - -```text -let u: list[number] = [1, 2, 3] -``` - -```text -let u: map[number] = { "one": 1, "two": 2, "three": 3 } -``` +**Cast:** `cast expr as type` -```text -let u: record[string, number, bool] = ["one", 1, true] -``` +List index: `expr[number]`. Map index: `expr[string]` or `expr.key`. -:::caution -Map keys must be strings. -::: +## Parameters -:::note -The type is optional for `let` statements. When undeclared, the value is not validated against any type constraints. -However, it is recommended to declare the type for better readability and to avoid surprises where the type is not what you expect. -::: +| Type | Description | +| :--- | :--- | +| `number` | float64. | +| `string` | UTF-8 string. | +| `trinary` | `true` \| `false` \| `unknown`. | +| `bool` | `true` \| `false` (subset of trinary). | +| `document` | JSON-like object. | +| `list[T]` | Ordered list of T; index by number. | +| `map[T]` | String keys, T values; keys must be strings. | +| `record[T1,T2,...]` | Fixed-length tuple. | -### Accessing Collection Elements +**Returns:** N/A for types. `cast` returns the value after validation against the target type (and constraints); fails if invalid. -You can access collection elements using the `[index]` syntax. The index must be a `string` for maps and a `number` for lists and records. +## Examples -```text -let u: list[number] = [1, 2, 3] -let first: number = u[0] -``` +### Basic Usage ```text -let u: map[number] = { "one": 1, "two": 2, "three": 3 } -let first: number = u["one"] +let u: number = 50 +let s: string = "hello" +let b: bool = true +let arr: list[number] = [1, 2, 3] +let m: map[number] = { "one": 1, "two": 2 } +let r: record[string, number, bool] = ["one", 1, true] ``` -For maps, you can also access elements using the `.` syntax. +### Advanced Usage ```text -let u: map[number] = { "one": 1, "two": 2, "three": 3 } -let first: number = u.one +let first: number = arr[0] +let one: number = m["one"] +let oneAlt: number = m.one +let x: number = cast "50" as number ``` -## Converting Types +## Behavior & Constraints -You can convert between types using the `cast .. as` construct. The result is validated against the new type constraints before returning the result. +- Type annotation on `let` is optional; when omitted, value is not validated against a type. +- Map keys must be strings. Division by zero aborts evaluation. Constraint failure aborts evaluation. -```text -let u: number = cast "50" as number -``` - -```text -let u: string = cast 50 as string -``` +## Constraints & Edge Cases -```text -let u: bool = cast "true" as bool -``` - -```text -let u: document = cast { "name": "John", "age": 30 } as document -``` +- Map keys must be strings. Access with `[index]`: number for list/record, string for map. +- `cast` validates against the target type and any constraints; failure aborts evaluation. diff --git a/src/content/docs/reference/typescript_modules/index.md b/src/content/docs/reference/typescript_modules/index.md index a40208e..3751f1c 100644 --- a/src/content/docs/reference/typescript_modules/index.md +++ b/src/content/docs/reference/typescript_modules/index.md @@ -1,384 +1,73 @@ --- title: Built-in TypeScript Modules -description: Complete reference for all built-in TypeScript modules in Sentrie. +description: "Reference for built-in @sentrie/* modules: import syntax and module list." --- -Sentrie provides a comprehensive set of built-in TypeScript modules for common operations. These modules are available under the `@sentrie/*` namespace and can be imported using the `use` statement in your policies. +# Built-in TypeScript Modules -## Quick Start +Built-in modules provide functions for hashing, encoding, time, JSON, regex, and more. Import with `use { ... } from @sentrie/module`; no quotes. For custom modules see [Writing custom TypeScript modules](/extensibility/writing-custom-typescript-modules). -Import and use built-in modules in your policies: +## Syntax ```text -namespace com/example/mypolicy - -policy mypolicy { - use { now } from @sentrie/time - use { sha256 } from @sentrie/hash - use { parse } from @sentrie/js as json - use { isValid } from @sentrie/json as jsonUtil - - fact data!: string - fact timestamp!: number - - rule processData = default false { - let hash = sha256(data) - let currentTime = now() - let jsonData = json.parse(data) - let isValid = jsonUtil.isValid(data) - yield hash != "" and currentTime > timestamp and isValid - } - - export decision of processData -} -``` - -## Module Categories - -### Data Manipulation - -#### [Collection](./sentrie/collection) - -List and map manipulation utilities. Functions are prefixed with `list_` for array operations and `map_` for object operations. - -**Key Functions:** - -- `list_includes`, `list_sort`, `list_unique`, `list_chunk` -- `map_keys`, `map_values`, `map_get`, `map_merge` - -#### [JavaScript Globals](./sentrie/js) - -Access to JavaScript globals (Math, String, Number, Date, JSON, Array) as individual functions. - -**Key Functions:** - -- **Math**: `round`, `floor`, `ceil`, `max`, `min`, `abs`, `sqrt`, `pow`, `sin`, `cos`, `tan`, etc. -- **String**: `length`, `fromCharCode` -- **Number**: `isNaN`, `parseInt`, `parseFloat`, `isFinite`, `isInteger` -- **Date**: `now`, `dateParse`, `UTC` -- **JSON**: `parse`, `stringify` -- **Array**: `isArray`, `from`, `of` - -#### [JSON](./sentrie/json) - -JSON validation utility. - -**Key Functions:** - -- `isValid` - Validates if a string is valid JSON - -### Cryptography & Security - -#### [Hash](./sentrie/hash) - -Comprehensive hash functions including MD5, SHA-1, SHA-256, SHA-512, and HMAC. - -**Key Functions:** - -- `md5`, `sha1`, `sha256`, `sha512`, `hmac` - -**Security Note:** MD5 and SHA-1 are cryptographically broken. Use SHA-256 or SHA-512 for secure hashing. - -#### [Crypto](./sentrie/crypto) - -Basic cryptographic utilities including SHA-256 hashing. - -**Key Functions:** - -- `sha256` - -#### [JWT](./sentrie/jwt) - -JSON Web Token decoding and verification utilities. **Note:** This module only decodes and verifies tokens; it does NOT create/generate tokens. - -**Key Functions:** - -- `decode`, `verify`, `getPayload`, `getHeader` - -**Supported Algorithms:** HS256, HS384, HS512 - -### Encoding & Decoding - -#### [Encoding](./sentrie/encoding) - -Various encoding and decoding utilities. Supports Base64, Hex, and URL encoding/decoding operations. - -**Key Functions:** - -- `base64Encode`, `base64Decode`, `base64UrlEncode`, `base64UrlDecode` -- `hexEncode`, `hexDecode` -- `urlEncode`, `urlDecode` - -### Network & Internet - -#### [Net](./sentrie/net) - -Network and IP address utilities for network-based policies. Supports both IPv4 and IPv6 addresses and CIDR notation. - -**Key Functions:** - -- `cidrContains`, `cidrIntersects`, `cidrIsValid`, `cidrExpand`, `cidrMerge` -- `parseIP`, `isIPv4`, `isIPv6`, `isPrivate`, `isPublic`, `isLoopback` - -#### [URL](./sentrie/url) - -URL parsing and manipulation utilities. **Note:** URL encoding/decoding is provided by the encoding module. - -**Key Functions:** - -- `parse`, `join`, `getHost`, `getPath`, `getQuery`, `isValid` - -### Date & Time - -#### [Time](./sentrie/time) - -Date and time manipulation utilities. All timestamps are Unix timestamps (seconds since epoch). - -**Key Functions:** - -- `now`, `parse`, `format`, `addDuration`, `subtractDuration` -- `isBefore`, `isAfter`, `isBetween` - -**Constants:** - -- `RFC3339`, `RFC3339Nano`, `RFC1123`, `RFC1123Z`, `RFC822`, `RFC822Z` - -### JavaScript Globals - -#### [JavaScript Globals](./sentrie/js) - -Access to JavaScript globals (Math, String, Number, Date, JSON, Array) as individual functions. This module provides direct access to standard JavaScript functions. - -**Key Functions:** - -- **Math**: `round`, `floor`, `ceil`, `max`, `min`, `abs`, `sqrt`, `pow`, `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `atan2`, `sinh`, `cosh`, `tanh`, `exp`, `log`, `log10`, `log2`, `random` -- **String**: `length`, `fromCharCode` -- **Number**: `isNaN`, `parseInt`, `parseFloat`, `isFinite`, `isInteger` -- **Date**: `now`, `dateParse`, `UTC` -- **JSON**: `parse`, `stringify` -- **Array**: `isArray`, `from`, `of` - -**Constants:** - -- `E`, `PI`, `LN2`, `LN10`, `LOG2E`, `LOG10E`, `SQRT2`, `SQRT1_2`, `MAX_VALUE`, `MIN_VALUE` - -### Pattern Matching - -#### [Regex](./sentrie/regex) - -Regular expression pattern matching and manipulation utilities. All patterns are compiled and cached for performance. - -**Key Functions:** - -- `match`, `find`, `findAll`, `replace`, `replaceAll`, `split` - -### Version Management - -#### [Semver](./sentrie/semver) - -Semantic version comparison and validation utilities. Supports the "v" prefix (e.g., `"v1.2.3"` is equivalent to `"1.2.3"`). - -**Key Functions:** - -- `compare`, `isValid`, `satisfies`, `stripPrefix` -- `major`, `minor`, `patch`, `prerelease`, `metadata` - -### Identifiers - -#### [UUID](./sentrie/uuid) - -Functions for generating UUIDs (Universally Unique Identifiers). - -**Key Functions:** - -- `v4()` - Random UUID (version 4) -- `v6()` - Time-ordered UUID (version 6) -- `v7()` - Time-ordered UUID with Unix timestamp (version 7) - -## Complete Module List - -| Module | Description | Category | -| ------------------------------------------- | ------------------------------------------------------------ | ------------------ | -| [@sentrie/collection](./sentrie/collection) | List and map manipulation utilities | Data Manipulation | -| [@sentrie/crypto](./sentrie/crypto) | Cryptographic functions (SHA-256) | Cryptography | -| [@sentrie/encoding](./sentrie/encoding) | Base64, Hex, and URL encoding/decoding | Encoding | -| [@sentrie/hash](./sentrie/hash) | Hash functions (MD5, SHA-1, SHA-256, SHA-512, HMAC) | Cryptography | -| [@sentrie/js](./sentrie/js) | JavaScript globals (Math, String, Number, Date, JSON, Array) | JavaScript Globals | -| [@sentrie/json](./sentrie/json) | JSON validation utility | Data Manipulation | -| [@sentrie/jwt](./sentrie/jwt) | JSON Web Token decoding and verification | Security | -| [@sentrie/net](./sentrie/net) | Network and IP address utilities | Network | -| [@sentrie/regex](./sentrie/regex) | Regular expression pattern matching | Pattern Matching | -| [@sentrie/semver](./sentrie/semver) | Semantic version comparison and validation | Version Management | -| [@sentrie/time](./sentrie/time) | Date and time manipulation | Date & Time | -| [@sentrie/url](./sentrie/url) | URL parsing and manipulation | Network | -| [@sentrie/uuid](./sentrie/uuid) | UUID generation (v4, v6, v7) | Identifiers | - -## Common Use Cases - -### Authentication & Authorization - -```text -namespace com/example/auth - -policy authentication { - use { sha256, hmac } from @sentrie/hash - use { decode, verify } from @sentrie/jwt - use { base64Decode } from @sentrie/encoding - - fact token!: string - fact secretKey!: string - fact passwordInput!: string - fact expectedHash!: string - - rule verifyToken = default false { - let isValid = jwt.verify(token, secretKey) - yield isValid - } - - rule verifyPassword = default false { - let hash = hash.sha256(passwordInput) - yield hash == expectedHash - } - - export decision of verifyToken - export decision of verifyPassword -} +use { fn1, fn2 } from @sentrie/module [ as alias ] ``` -### Data Validation +Built-in modules: `@sentrie/module` (no quotes). Default alias is the last path segment (e.g. `time` for `@sentrie/time`). -```text -namespace com/example/validation +## Parameters -policy validation { - use { match } from @sentrie/regex - use { length } from @sentrie/js as str - use { isValid } from @sentrie/json as jsonUtil +| Element | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| `fn1`, `fn2` | identifiers | Yes | Exported function names from the module. | +| `@sentrie/module` | path | Yes | Module name (e.g. `@sentrie/hash`, `@sentrie/time`). | +| `as alias` | identifier | No | Name used in policy (e.g. `alias.fn1()`). | - fact email!: string - fact jsonData!: string +**Returns:** N/A (import). Function return types are per module; see linked pages. - rule validateEmail = default false { - let emailPattern = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" - let emailLength = str.length(email) - yield regex.match(emailPattern, email) and emailLength > 0 - } - - rule validateJson = default false { - yield jsonUtil.isValid(jsonData) - } - - export decision of validateEmail - export decision of validateJson -} -``` +## Examples -### Network Access Control +### Basic Usage ```text -namespace com/example/network - -policy network { - use { cidrContains, isPrivate, parseIP } from @sentrie/net - use { getHost, isValid } from @sentrie/url - - fact clientIp!: string - fact allowedCidr!: string - fact requestUrl!: string - - rule checkAccess = default false { - let ip = net.parseIP(clientIp) - let isAllowed = net.cidrContains(allowedCidr, clientIp) - let isNotPrivate = not net.isPrivate(clientIp) - yield ip != null and isAllowed and isNotPrivate - } - - rule validateUrl = default false { - let isValidUrl = url.isValid(requestUrl) - let host = url.getHost(requestUrl) - yield isValidUrl and host != "" - } - - export decision of checkAccess - export decision of validateUrl -} -``` - -### Time-Based Policies - -```text -namespace com/example/time - -policy time { - use { now, isBefore, addDuration, format } from @sentrie/time - - fact tokenExpiry!: number - fact sessionStart!: number - - rule checkTokenExpiry = default false { - let currentTime = time.now() - let isExpired = time.isBefore(tokenExpiry, currentTime) - yield not isExpired - } - - rule checkSessionTimeout = default false { - let currentTime = time.now() - let sessionTimeout = time.addDuration(sessionStart, "1h") - let isExpired = time.isBefore(sessionTimeout, currentTime) - yield not isExpired - } - - export decision of checkTokenExpiry - export decision of checkSessionTimeout -} -``` - -## Import Syntax - -All modules use the same import syntax: - -```text -use { function1, function2 } from @sentrie/module -``` - -:::note -Built-in `@sentrie/*` modules do not use quotes. Local TypeScript files use quotes for relative paths. -::: - -You can optionally use an alias: - -```text -use { function1, function2 } from @sentrie/module +use { now } from @sentrie/time +use { sha256 } from @sentrie/hash +use { isValid } from @sentrie/json as jsonUtil ``` -If no alias is specified, the default alias is the last part of the module path. - -## Module Aliasing - -When importing from a module, the default alias is the last part of the module path: +### Advanced Usage ```text use { now } from @sentrie/time --- Default alias is "time" (last part of @sentrie/time) --- Use as: time.now() +use { sha256 } from @sentrie/hash +use { parse } from @sentrie/js as json +let t = time.now() +let h = sha256(data) +let ok = jsonUtil.isValid(data) ``` -## Best Practices - -1. **Use appropriate modules** - Choose the right module for your use case (e.g., use `@sentrie/hash` for secure hashing instead of `@sentrie/crypto`) - -2. **Security considerations** - Use SHA-256 or SHA-512 for secure hashing. Avoid MD5 and SHA-1 for security-sensitive operations. - -3. **Performance** - Regex patterns are compiled and cached, so repeated use of the same pattern is efficient. +## Module List -4. **Type safety** - All modules provide full TypeScript type definitions for better error detection and IntelliSense support. +| Module | Description | +| :--- | :--- | +| [@sentrie/collection](/reference/typescript_modules/sentrie/collection) | List/map utilities (`list_*`, `map_*`) | +| [@sentrie/crypto](/reference/typescript_modules/sentrie/crypto) | SHA-256 | +| [@sentrie/encoding](/reference/typescript_modules/sentrie/encoding) | Base64, hex, URL encode/decode | +| [@sentrie/hash](/reference/typescript_modules/sentrie/hash) | MD5, SHA-1, SHA-256, SHA-512, HMAC | +| [@sentrie/js](/reference/typescript_modules/sentrie/js) | Math, String, Number, Date, JSON, Array globals | +| [@sentrie/json](/reference/typescript_modules/sentrie/json) | `isValid` (JSON validation) | +| [@sentrie/jwt](/reference/typescript_modules/sentrie/jwt) | Decode/verify JWT (HS256/384/512) | +| [@sentrie/net](/reference/typescript_modules/sentrie/net) | CIDR, parseIP, isPrivate, etc. | +| [@sentrie/regex](/reference/typescript_modules/sentrie/regex) | match, find, replace, split | +| [@sentrie/semver](/reference/typescript_modules/sentrie/semver) | compare, isValid, satisfies, major/minor/patch | +| [@sentrie/time](/reference/typescript_modules/sentrie/time) | now, parse, format, addDuration, isBefore, etc. | +| [@sentrie/url](/reference/typescript_modules/sentrie/url) | parse, join, getHost, getPath, getQuery, isValid | +| [@sentrie/uuid](/reference/typescript_modules/sentrie/uuid) | v4, v6, v7 | -5. **Error handling** - Most functions throw errors on invalid input. Always validate inputs before calling module functions. +## Behavior & Constraints -6. **Module organization** - Use aliases when importing multiple functions from the same module to keep code readable. +- Only the listed functions can be imported. Paths are resolved at load time. Invalid function names or modules cause errors. -## See Also +## Constraints & Edge Cases -- [Using TypeScript](/reference/using-typescript) - Complete guide to using TypeScript in Sentrie -- [Policy Pack Structure](/structure-of-a-policy-pack/overview) - Learn about organizing your policy pack -- [Policy Language Reference](/reference) - Complete reference for the Sentrie policy language +- Built-in `@sentrie/*` modules do not use quotes. Local files use quoted paths (e.g. `"./utils.ts"`). JWT module only decodes/verifies; it does not create tokens. Prefer SHA-256/SHA-512 over MD5/SHA-1 for security. diff --git a/src/content/docs/reference/typescript_modules/sentrie/collection.md b/src/content/docs/reference/typescript_modules/sentrie/collection.md index 46a0701..d3ffec4 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/collection.md +++ b/src/content/docs/reference/typescript_modules/sentrie/collection.md @@ -332,3 +332,11 @@ policy mypolicy { export decision of myrule } ``` + +## Behavior & Constraints + +- List functions use `list_` prefix; map functions use `map_` prefix. Operations do not mutate the original collection. + +## Constraints & Edge Cases + +- Throws if input is not an array (for list_*) or not an object (for map_*). Deep equality used where applicable. diff --git a/src/content/docs/reference/typescript_modules/sentrie/crypto.md b/src/content/docs/reference/typescript_modules/sentrie/crypto.md index 45a8cb3..41b9a3d 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/crypto.md +++ b/src/content/docs/reference/typescript_modules/sentrie/crypto.md @@ -45,6 +45,10 @@ policy mypolicy { } ``` -## See Also +## Behavior & Constraints -- [@sentrie/hash](./hash) - For additional hash algorithms (MD5, SHA-1, SHA-512, HMAC) +- Returns SHA-256 hash as hexadecimal string. For more algorithms use [@sentrie/hash](/reference/typescript_modules/sentrie/hash). + +## Constraints & Edge Cases + +- Input must be string. Invalid or unsupported input may throw. diff --git a/src/content/docs/reference/typescript_modules/sentrie/encoding.md b/src/content/docs/reference/typescript_modules/sentrie/encoding.md index 3fc8873..796b3e1 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/encoding.md +++ b/src/content/docs/reference/typescript_modules/sentrie/encoding.md @@ -177,3 +177,11 @@ policy mypolicy { export decision of encodeData } ``` + +## Behavior & Constraints + +- Base64/hex/URL encode decode; input and output are strings. Invalid Base64/hex input may throw. + +## Constraints & Edge Cases + +- base64Url omits padding; URL encoding is percent-encoding. Decode errors throw. diff --git a/src/content/docs/reference/typescript_modules/sentrie/hash.md b/src/content/docs/reference/typescript_modules/sentrie/hash.md index 0a7d5cd..d0c4a36 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/hash.md +++ b/src/content/docs/reference/typescript_modules/sentrie/hash.md @@ -136,8 +136,11 @@ policy mypolicy { } ``` -## Security Recommendations +## Behavior & Constraints -1. **Use SHA-256 or SHA-512** for secure hashing instead of MD5 or SHA-1 -2. **Use HMAC** for message authentication with a secret key -3. **Store hashed passwords** using SHA-256 or SHA-512 with proper salt (consider using HMAC with a secret key) +- All hash functions return hexadecimal-encoded strings. Input is string; HMAC takes algorithm name, message, and secret key. +- Use SHA-256 or SHA-512 for security; MD5 and SHA-1 are cryptographically broken. + +## Constraints & Edge Cases + +- Invalid input (e.g. non-hex for decode) may throw. HMAC algorithm must be supported (e.g. sha256). diff --git a/src/content/docs/reference/typescript_modules/sentrie/js.md b/src/content/docs/reference/typescript_modules/sentrie/js.md index bf59991..e2cc519 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/js.md +++ b/src/content/docs/reference/typescript_modules/sentrie/js.md @@ -473,3 +473,11 @@ policy mypolicy { export decision of processData } ``` + +## Behavior & Constraints + +- Exposes Math, String, Number, Date, JSON, Array as individual functions. Behavior matches JavaScript; angles in radians for trig. + +## Constraints & Edge Cases + +- Invalid input (e.g. NaN, invalid date) may produce NaN or throw per JS semantics. random() is not cryptographically secure. diff --git a/src/content/docs/reference/typescript_modules/sentrie/json.md b/src/content/docs/reference/typescript_modules/sentrie/json.md index 538f9e8..9baf256 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/json.md +++ b/src/content/docs/reference/typescript_modules/sentrie/json.md @@ -1,75 +1,51 @@ --- title: "@sentrie/json" -description: JSON validation utility +description: JSON validation. For parse/stringify use @sentrie/js. --- -The `@sentrie/json` module provides JSON validation utilities. For JSON parsing and stringification, use the `@sentrie/js` module. +# @sentrie/json -## Usage +Provides `isValid` to check if a string is valid JSON. For parsing and stringifying use [@sentrie/js](/reference/typescript_modules/sentrie/js). + +## Syntax ```text -use { isValid } from @sentrie/json +use { isValid } from @sentrie/json [ as alias ] +alias.isValid(str) ``` -## Functions - -### `isValid(str: string): boolean` - -Validates whether a string is valid JSON. +## Parameters -**Parameters:** +| Name | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| `str` | string | Yes | String to validate as JSON. | -- `str` - The JSON string to validate +**Returns:** `boolean` — true if the string is valid JSON, false otherwise. -**Returns:** `true` if the string is valid JSON, `false` otherwise +## Examples -**Example:** +### Basic Usage ```text -use { isValid } from @sentrie/json - -let jsonStr = '{"name": "John", "age": 30}' -let isValid = json.isValid(jsonStr) // true - -let invalidStr = '{"name": "John", "age":}' -let isInvalid = json.isValid(invalidStr) // false +use { isValid } from @sentrie/json as jsonUtil +let ok = jsonUtil.isValid('{"name": "John", "age": 30}') +let bad = jsonUtil.isValid('{"name": "John", "age":}') ``` -## JSON Parsing and Stringification - -For JSON parsing and stringification, use the `@sentrie/js` module: +### Advanced Usage ```text +use { isValid } from @sentrie/json as jsonUtil use { parse, stringify } from @sentrie/js as json - -let obj = json.parse('{"name": "John", "age": 30}') -let str = json.stringify({"name": "John", "age": 30}) +rule processData = default false { + yield jsonUtil.isValid(data) and json.parse(data) != null +} ``` -See the [@sentrie/js](/reference/typescript_modules/sentrie/js) documentation for more information. +## Behavior & Constraints -## Complete Example +- Only validates syntax; does not parse. Use `@sentrie/js` for `parse` and `stringify`. -```text -namespace com/example/mypolicy - -policy mypolicy { - use { isValid } from @sentrie/json as jsonUtil - use { parse, stringify } from @sentrie/js as json - - fact data!: string - - rule processData = default false { - let isValid = jsonUtil.isValid(data) - if isValid { - let parsed = json.parse(data) - let serialized = json.stringify(parsed) - yield serialized != "" - } else { - yield false - } - } - - export decision of processData -} -``` +## Constraints & Edge Cases + +- Empty string is not valid JSON. Invalid UTF-8 or malformed structure returns false. diff --git a/src/content/docs/reference/typescript_modules/sentrie/jwt.md b/src/content/docs/reference/typescript_modules/sentrie/jwt.md index 9f0a8d9..38634b0 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/jwt.md +++ b/src/content/docs/reference/typescript_modules/sentrie/jwt.md @@ -149,3 +149,11 @@ header.payload.signature - **Header** - Contains metadata about the token (algorithm, type) - **Payload** - Contains the claims (data) - **Signature** - Used to verify the token hasn't been tampered with + +## Behavior & Constraints + +- Decode and verify only; does not create tokens. Supported algorithms: HS256, HS384, HS512. Always verify signature before trusting payload. + +## Constraints & Edge Cases + +- Invalid token format, bad signature, or unsupported algorithm throws. Use verify() or decode() with secret; do not rely on getPayload() alone. diff --git a/src/content/docs/reference/typescript_modules/sentrie/net.md b/src/content/docs/reference/typescript_modules/sentrie/net.md index c486c3c..52311ed 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/net.md +++ b/src/content/docs/reference/typescript_modules/sentrie/net.md @@ -257,3 +257,11 @@ policy mypolicy { export decision of checkAccess } ``` + +## Behavior & Constraints + +- IPv4 and IPv6; CIDR and single IP. parseIP returns parsed representation or null. Invalid CIDR/IP throws. + +## Constraints & Edge Cases + +- Invalid CIDR or IP throws. Use isPrivate, isPublic, isLoopback for classification. diff --git a/src/content/docs/reference/typescript_modules/sentrie/regex.md b/src/content/docs/reference/typescript_modules/sentrie/regex.md index c4df32c..f93f424 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/regex.md +++ b/src/content/docs/reference/typescript_modules/sentrie/regex.md @@ -199,3 +199,11 @@ let alnumPattern = "^[a-zA-Z0-9_]+$" - All patterns are compiled and cached for performance - Repeated use of the same pattern in a single execution context will use the cached compiled pattern - Patterns are automatically cleaned up after execution + +## Behavior & Constraints + +- Patterns are strings; compiled and cached per execution. match/find/findAll/replace/replaceAll/split follow standard regex semantics. + +## Constraints & Edge Cases + +- Invalid pattern throws. Replace/split return new string/list; original unchanged. diff --git a/src/content/docs/reference/typescript_modules/sentrie/semver.md b/src/content/docs/reference/typescript_modules/sentrie/semver.md index 6068207..5be05d1 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/semver.md +++ b/src/content/docs/reference/typescript_modules/sentrie/semver.md @@ -233,3 +233,11 @@ Semantic versions follow the format: `MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]` - **PATCH** - Incremented for backwards-compatible bug fixes - **PRERELEASE** - Optional prerelease identifier (e.g., `alpha`, `beta`, `rc.1`) - **BUILD** - Optional build metadata (e.g., `+001`, `+exp.sha.5114f85`) + +## Behavior & Constraints + +- "v" prefix is stripped; compare returns -1, 0, 1. satisfies uses npm-style range. major/minor/patch/prerelease/metadata extract components. + +## Constraints & Edge Cases + +- Invalid version string throws. stripPrefix removes leading "v". diff --git a/src/content/docs/reference/typescript_modules/sentrie/time.md b/src/content/docs/reference/typescript_modules/sentrie/time.md index 7635b6d..427d1d2 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/time.md +++ b/src/content/docs/reference/typescript_modules/sentrie/time.md @@ -242,3 +242,11 @@ Common format patterns: - `"15:04:05"` - Time: `15:04:05` - `"2006-01-02 15:04:05"` - Date and time: `2006-01-02 15:04:05` - `"Mon, 02 Jan 2006 15:04:05 MST"` - RFC1123 format + +## Behavior & Constraints + +- Timestamps are Unix seconds. now() is fixed within an execution. addDuration/subtractDuration use duration strings (e.g. "1h", "30m"). isBefore/isAfter/isBetween compare timestamps. + +## Constraints & Edge Cases + +- Invalid format or duration may throw. Constants (RFC3339, etc.) are format layout strings. diff --git a/src/content/docs/reference/typescript_modules/sentrie/url.md b/src/content/docs/reference/typescript_modules/sentrie/url.md index a4e6c89..8e30f13 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/url.md +++ b/src/content/docs/reference/typescript_modules/sentrie/url.md @@ -193,3 +193,11 @@ policy mypolicy { - Relative URLs (starting with `"/"`) are supported - The `parse` function provides comprehensive URL parsing with all components - The `join` function properly resolves relative paths against base URLs + +## Behavior & Constraints + +- parse returns ParsedURL; getHost/getPath/getQuery work on string URLs. Encoding/decoding is in @sentrie/encoding. + +## Constraints & Edge Cases + +- Invalid URL may throw or return invalid result. isValid checks format. Relative URLs (starting with /) supported. diff --git a/src/content/docs/reference/typescript_modules/sentrie/uuid.md b/src/content/docs/reference/typescript_modules/sentrie/uuid.md index aca5572..68ff8cb 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/uuid.md +++ b/src/content/docs/reference/typescript_modules/sentrie/uuid.md @@ -1,134 +1,56 @@ --- title: "@sentrie/uuid" -description: UUID generation +description: UUID generation (v4, v6, v7). --- -The `@sentrie/uuid` module provides functions for generating UUIDs (Universally Unique Identifiers). +# @sentrie/uuid -## Usage +Generates UUIDs. v4: random; v6/v7: time-ordered. Use when you need unique identifiers or time-ordered IDs for indexing. -```text -use { v4, v6, v7 } from @sentrie/uuid -``` - -## Functions - -### `v4(): string` - -Generates a version 4 UUID (random UUID). Version 4 UUIDs are randomly generated and provide strong uniqueness guarantees. - -**Returns:** A UUID string in standard format (e.g., `"550e8400-e29b-41d4-a716-446655440000"`) - -**Example:** - -```text -use { v4 } from @sentrie/uuid -let uuid = uuid.v4() // "550e8400-e29b-41d4-a716-446655440000" -``` - -### `v6(): string` - -Generates a version 6 UUID (time-ordered UUID). Version 6 UUIDs are time-ordered and provide better database indexing performance. - -**Returns:** A UUID string in standard format (e.g., `"1b21dd213814000-8000-6000-0000-000000000000"`) - -**Throws:** Error if UUID generation fails - -**Example:** +## Syntax ```text -use { v6 } from @sentrie/uuid -let uuid = uuid.v6() // Time-ordered UUID +use { v4, v6, v7 } from @sentrie/uuid [ as alias ] +alias.v4() +alias.v6() +alias.v7() ``` -### `v7(): string` +## Parameters -Generates a version 7 UUID (time-ordered UUID with Unix timestamp). Version 7 UUIDs are time-ordered and include a Unix timestamp for better sorting. +| Function | Parameters | Required | Description | +| :--- | :--- | :--- | :--- | +| `v4()` | none | — | Random UUID (version 4). | +| `v6()` | none | — | Time-ordered UUID (version 6). | +| `v7()` | none | — | Time-ordered UUID with Unix timestamp (version 7). | -**Returns:** A UUID string in standard format (e.g., `"017f22e2-79b0-7cc3-8000-383fb6ef7b1a"`) +**Returns:** `string` — UUID in form `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`. Throws on generation failure. -**Throws:** Error if UUID generation fails +## Examples -**Example:** +### Basic Usage ```text -use { v7 } from @sentrie/uuid -let uuid = uuid.v7() // Time-ordered UUID with Unix timestamp +use { v4, v7 } from @sentrie/uuid +let id = uuid.v4() +let timeId = uuid.v7() ``` -## UUID Versions - -### Version 4 (Random) - -- **Use case:** General purpose, when you need strong uniqueness guarantees -- **Characteristics:** Randomly generated, no ordering -- **Performance:** Good for general use, but not optimal for database indexing - -### Version 6 (Time-Ordered) - -- **Use case:** When you need time-ordered UUIDs for better database performance -- **Characteristics:** Time-ordered, better for database indexing -- **Performance:** Better for database primary keys due to time ordering - -### Version 7 (Time-Ordered with Unix Timestamp) - -- **Use case:** When you need time-ordered UUIDs with Unix timestamp for sorting -- **Characteristics:** Time-ordered with Unix timestamp, best for sorting -- **Performance:** Best for database indexing and sorting operations - -## Complete Example +### Advanced Usage ```text -namespace com/example/resources - -policy mypolicy { - use { v4, v7 } from @sentrie/uuid - fact resourceType!: string - - rule createResource = default false { - let id = uuid.v4() - let timeOrderedId = uuid.v7() - yield id != "" and timeOrderedId != "" - } - - export decision of createResource +use { v4, v7 } from @sentrie/uuid +rule createResource = default false { + let id = uuid.v4() + let timeOrderedId = uuid.v7() + yield id != "" and timeOrderedId != "" } ``` -## When to Use Each Version - -### Use Version 4 (v4) when: - -- You need general-purpose unique identifiers -- Ordering doesn't matter -- You want maximum randomness - -### Use Version 6 (v6) when: - -- You need time-ordered UUIDs -- Database indexing performance is important -- You want UUIDs that are roughly ordered by creation time - -### Use Version 7 (v7) when: - -- You need time-ordered UUIDs with Unix timestamp -- Sorting by creation time is important -- You want the best database indexing performance -- You need to extract timestamp information from the UUID - -## UUID Format - -All UUID versions follow the standard format: - -``` -xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -``` +## Behavior & Constraints -Where each `x` is a hexadecimal digit (0-9, a-f). +- v4: random; no ordering. v6/v7: time-ordered for better DB indexing/sorting. v7 includes Unix timestamp. -## Security Considerations +## Constraints & Edge Cases -- UUIDs are not cryptographically secure random numbers -- For security-sensitive applications, use proper cryptographic random number generators -- UUIDs are designed for uniqueness, not security -- Version 4 UUIDs provide good randomness but are not suitable for cryptographic purposes +- UUIDs are not cryptographically secure; use proper CSPRNG for security-sensitive randomness. Generation failure throws. From 5d111c764320bf9fc75040b03b5b46245426a976 Mon Sep 17 00:00:00 2001 From: Binaek Sarkar Date: Sat, 21 Feb 2026 00:15:01 +0530 Subject: [PATCH 02/21] Enhance documentation clarity and consistency across multiple sections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit improves the Sentrie documentation by refining formatting, enhancing descriptions, and ensuring consistency in terminology. **Key Changes:** - **Parameter Tables**: Standardized formatting for parameter tables across various documentation files for better readability. - **Return Descriptions**: Updated return descriptions to use consistent punctuation, changing from "—" to "-" for clarity. - **Content Refinements**: Made minor adjustments to text for improved flow and clarity, including removing unnecessary line breaks and ensuring concise explanations. **Benefits:** - Improved readability and user experience for documentation. - Consistent formatting enhances the professional appearance of the documentation. ## Testing - Documentation reviewed for accuracy and consistency. - All changes verified for correct rendering and functionality. --- docs-template.md | 25 +++++++++++-------- .../docs/getting-started/enforcement.md | 10 ++++---- .../docs/getting-started/installation.md | 10 -------- .../docs/getting-started/quick-start.md | 8 +----- src/content/docs/index.mdx | 9 +++---- .../pattern-matching-conditionals.md | 16 ++++++------ .../language-concepts/type-system-shapes.md | 13 +++++----- src/content/docs/reference/facts.md | 14 +++++------ .../typescript_modules/sentrie/json.md | 8 +++--- .../typescript_modules/sentrie/uuid.md | 12 ++++----- 10 files changed, 57 insertions(+), 68 deletions(-) diff --git a/docs-template.md b/docs-template.md index befa024..8dfb1ae 100644 --- a/docs-template.md +++ b/docs-template.md @@ -10,22 +10,26 @@ ## Parameters -| Name | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| `paramName` | `Type` | Yes/No | A clear, concise explanation of what this input does. | +| Name | Type | Required | Description | +| :---------- | :----- | :------- | :---------------------------------------------------- | +| `paramName` | `Type` | Yes/No | A clear, concise explanation of what this input does. | -**Returns:** `ReturnType` — [A brief description of the output, e.g., "A boolean indicating whether the constraint was met."] +**Returns:** `ReturnType` - [A brief description of the output, e.g., "A boolean indicating whether the constraint was met."] ## Examples ### Basic Usage + [One sentence explaining what this example demonstrates.] + ```typescript // Minimum reproducible code block. Use realistic variable names. ``` ### Advanced Usage + [One sentence explaining the complexity or edge case shown here.] + ```typescript // Example showing composition, complex input, or error handling. ``` @@ -34,11 +38,12 @@ > **Note:** [Optional: A high-visibility callout for the most critical thing a human needs to know before using this feature.] -* **[Constraint 1]:** e.g., Failing constraint validation will abort evaluation immediately. -* **[Limitation]:** e.g., Does not support dynamic key generation. All keys must be explicitly declared. -* **[Compatibility]:** e.g., Cannot be used in conjunction with [Feature Y]. +- **[Constraint 1]:** e.g., Failing constraint validation will abort evaluation immediately. +- **[Limitation]:** e.g., Does not support dynamic key generation. All keys must be explicitly declared. +- **[Compatibility]:** e.g., Cannot be used in conjunction with [Feature Y]. ## Constraints & Edge Cases -* [Limitation 1: e.g., "Failing constraint validation will abort evaluation immediately."] -* [Limitation 2: e.g., "Does not support dynamic key generation."] -* [Mutual Exclusions: e.g., "Cannot be used in conjunction with X."] + +- [Limitation 1: e.g., "Failing constraint validation will abort evaluation immediately."] +- [Limitation 2: e.g., "Does not support dynamic key generation."] +- [Mutual Exclusions: e.g., "Cannot be used in conjunction with X."] diff --git a/src/content/docs/getting-started/enforcement.md b/src/content/docs/getting-started/enforcement.md index 79e9b8b..b018276 100644 --- a/src/content/docs/getting-started/enforcement.md +++ b/src/content/docs/getting-started/enforcement.md @@ -7,12 +7,12 @@ description: Enforcing policies using Sentrie Sentrie is a **deterministic policy decision engine**. It evaluates structured inputs against rules and returns `true`, `false`, or `unknown`. That's it. -The engine is **pure** and **side-effect-free**—no mutations, no API calls, no state changes. This makes it safe to embed deep in your stack: IAM systems, API gateways, control planes, multi-tenant services, anywhere you need fast, reliable authorization decisions. +The engine is **pure** and **side-effect-free**-no mutations, no API calls, no state changes. This makes it safe to embed deep in your stack: IAM systems, API gateways, control planes, multi-tenant services, anywhere you need fast, reliable authorization decisions. Because enforcement lives outside the policy, the same rules work across different systems with different enforcement modes - whether you're blocking requests, redacting data, or triggering step-up auth. :::note[Sentrie makes decisions] -Sentrie focuses on **decision quality** — your systems handle enforcement. +Sentrie focuses on **decision quality** - your systems handle enforcement. ::: ## Why Enforcement is External @@ -27,9 +27,9 @@ Different platforms enforce differently: Keeping the engine pure gives you three guarantees: -- **Determinism** — same input = same output, always -- **Reproducibility** — decisions are safe to replay and audit -- **Portability** — runs identically everywhere +- **Determinism** - same input = same output, always +- **Reproducibility** - decisions are safe to replay and audit +- **Portability** - runs identically everywhere This is what makes Sentrie safe for **real-time**, **latency-sensitive** paths. diff --git a/src/content/docs/getting-started/installation.md b/src/content/docs/getting-started/installation.md index a9657da..23141a0 100644 --- a/src/content/docs/getting-started/installation.md +++ b/src/content/docs/getting-started/installation.md @@ -13,16 +13,6 @@ Convenience scripts are provided for macOS, Linux, and Windows. ### On macOS -**Recommended: Homebrew Formula** - -```bash -brew install sentrie-sh/tap/sentrie -``` - -> **Why Formulas instead of Casks?** macOS Gatekeeper flags binaries installed via Homebrew Casks, requiring users to manually remove quarantine attributes or approve the binary in System Settings. Formulas are treated as regular binaries by Gatekeeper, avoiding these security warnings and providing a seamless installation experience. - -**Alternative: Install script** - ```bash curl -fsSL https://sentrie.sh/install.sh | bash ``` diff --git a/src/content/docs/getting-started/quick-start.md b/src/content/docs/getting-started/quick-start.md index b7931db..1f76516 100644 --- a/src/content/docs/getting-started/quick-start.md +++ b/src/content/docs/getting-started/quick-start.md @@ -9,13 +9,7 @@ Install the Sentrie binary and evaluate a policy from the command line. This pag ## Syntax -Install (macOS with Homebrew): - -```bash -brew install sentrie-sh/tap/sentrie -``` - -Install (macOS, Linux, WSL2 — install script): +Install (macOS, Linux, WSL2): ```bash curl -fsSL https://sentrie.sh/install.sh | bash diff --git a/src/content/docs/index.mdx b/src/content/docs/index.mdx index 28f5063..b8552fc 100644 --- a/src/content/docs/index.mdx +++ b/src/content/docs/index.mdx @@ -6,7 +6,7 @@ description: Sentrie is an open-source policy evaluation engine with a `Rich Exp template: splash pagefind: false hero: - tagline: Sentrie is a modern policy engine built for today’s applications — with a clean expression language, a powerful type system, first-class Typescript module support, and deterministic evaluation. + tagline: Sentrie is a modern policy engine built for today’s applications - with a clean expression language, a powerful type system, first-class Typescript module support, and deterministic evaluation. image: file: ../../assets/freeze.svg actions: @@ -25,8 +25,8 @@ import { Card, CardGrid } from "@astrojs/starlight/components"; - Install Sentrie and run your first policy evaluation. Installation and - basic usage. + Install Sentrie and run your first policy evaluation. Installation and basic + usage.
Learn more
@@ -54,8 +54,7 @@ import { Card, CardGrid } from "@astrojs/starlight/components"; Learn more - Run Sentrie as a service with a well-defined HTTP API for policy - evaluation. + Run Sentrie as a service with a well-defined HTTP API for policy evaluation.
Learn more
diff --git a/src/content/docs/language-concepts/pattern-matching-conditionals.md b/src/content/docs/language-concepts/pattern-matching-conditionals.md index ec65b24..33fba86 100644 --- a/src/content/docs/language-concepts/pattern-matching-conditionals.md +++ b/src/content/docs/language-concepts/pattern-matching-conditionals.md @@ -11,7 +11,7 @@ Sentrie uses trinary logic (`true`, `false`, `unknown`) and provides the ternary **Ternary:** `condition ? trueValue : falseValue` -**Elvis:** `expression ?: defaultValue` (equivalent to `expression ? expression : defaultValue`) +**Elvis:** `expression ?: defaultValue` (equivalent to `expression ? expression : defaultValue`) **Pattern match:** `string matches pattern` @@ -21,13 +21,13 @@ Sentrie uses trinary logic (`true`, `false`, `unknown`) and provides the ternary ## Parameters -| Construct | Left | Right | Description | -| :--- | :--- | :--- | :--- | -| `? :` | condition (trinary) | trueValue, falseValue | If condition is truthy, result is trueValue; else falseValue. | -| `?:` | expression | defaultValue | If expression is truthy, result is expression; else defaultValue. | -| `matches` | string | string (regex) | True if string matches the regex pattern. | -| `is defined` | any | — | True if value is not undefined. | -| `is empty` | string/list/map | — | True if empty (e.g. `""`, `[]`, `{}`). | +| Construct | Left | Right | Description | +| :----------- | :------------------ | :-------------------- | :---------------------------------------------------------------- | +| `? :` | condition (trinary) | trueValue, falseValue | If condition is truthy, result is trueValue; else falseValue. | +| `?:` | expression | defaultValue | If expression is truthy, result is expression; else defaultValue. | +| `matches` | string | string (regex) | True if string matches the regex pattern. | +| `is defined` | any | - | True if value is not undefined. | +| `is empty` | string/list/map | - | True if empty (e.g. `""`, `[]`, `{}`). | **Returns:** For ternary/Elvis: the selected value (any type). For `matches`, `is defined`, `is empty`: boolean (or trinary where applicable). Non-truthy for Elvis includes `false`, `null`, `undefined` (treated as unknown), `0`, `""`, empty collections. diff --git a/src/content/docs/language-concepts/type-system-shapes.md b/src/content/docs/language-concepts/type-system-shapes.md index e96bf95..853798e 100644 --- a/src/content/docs/language-concepts/type-system-shapes.md +++ b/src/content/docs/language-concepts/type-system-shapes.md @@ -14,6 +14,7 @@ The type system defines values (primitives, collections, documents) and structur **Collections:** `list[T]` | `map[T]` | `record[T1, T2, ...]` **Shape (data model):** + ```text shape Name { field!: type @@ -30,12 +31,12 @@ shape Name { ## Parameters -| Concept | Required | Description | -| :--- | :--- | :--- | -| Field `!` | No | Required non-null; field must be present and not null. | -| Field `?` | No | Optional; field may be omitted. | -| No marker | — | Required but may be null. | -| `with Base` | No | Shape inherits all fields of Base plus its own. | +| Concept | Required | Description | +| :---------- | :------- | :----------------------------------------------------- | +| Field `!` | No | Required non-null; field must be present and not null. | +| Field `?` | No | Optional; field may be omitted. | +| No marker | - | Required but may be null. | +| `with Base` | No | Shape inherits all fields of Base plus its own. | **Returns:** N/A (type system). Constraint validation fails at runtime if a value does not meet the type or constraints; evaluation aborts. diff --git a/src/content/docs/reference/facts.md b/src/content/docs/reference/facts.md index cb6bf5d..8337ee1 100644 --- a/src/content/docs/reference/facts.md +++ b/src/content/docs/reference/facts.md @@ -16,13 +16,13 @@ fact name? : type [ as alias ] [ default expr ] -- optional ## Parameters -| Part | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| `name` | identifier | Yes | Declaration name. | -| `name?` | — | No | `?` makes the fact optional. | -| `type` | shape/primitive | Yes | Type of the fact value. | -| `as alias` | identifier | No | Name used in the policy body; default is `name`. | -| `default expr` | expression | No | Only for optional facts; used when fact is omitted. | +| Part | Type | Required | Description | +| :------------- | :-------------- | :------- | :-------------------------------------------------- | +| `name` | identifier | Yes | Declaration name. | +| `name?` | - | No | `?` makes the fact optional. | +| `type` | shape/primitive | Yes | Type of the fact value. | +| `as alias` | identifier | No | Name used in the policy body; default is `name`. | +| `default expr` | expression | No | Only for optional facts; used when fact is omitted. | **Returns:** N/A (declaration). At evaluation time, fact names (or aliases) are bound to the provided JSON input. diff --git a/src/content/docs/reference/typescript_modules/sentrie/json.md b/src/content/docs/reference/typescript_modules/sentrie/json.md index 9baf256..ccc1857 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/json.md +++ b/src/content/docs/reference/typescript_modules/sentrie/json.md @@ -16,11 +16,11 @@ alias.isValid(str) ## Parameters -| Name | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| `str` | string | Yes | String to validate as JSON. | +| Name | Type | Required | Description | +| :---- | :----- | :------- | :-------------------------- | +| `str` | string | Yes | String to validate as JSON. | -**Returns:** `boolean` — true if the string is valid JSON, false otherwise. +**Returns:** `boolean` - true if the string is valid JSON, false otherwise. ## Examples diff --git a/src/content/docs/reference/typescript_modules/sentrie/uuid.md b/src/content/docs/reference/typescript_modules/sentrie/uuid.md index 68ff8cb..5222598 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/uuid.md +++ b/src/content/docs/reference/typescript_modules/sentrie/uuid.md @@ -18,13 +18,13 @@ alias.v7() ## Parameters -| Function | Parameters | Required | Description | -| :--- | :--- | :--- | :--- | -| `v4()` | none | — | Random UUID (version 4). | -| `v6()` | none | — | Time-ordered UUID (version 6). | -| `v7()` | none | — | Time-ordered UUID with Unix timestamp (version 7). | +| Function | Parameters | Required | Description | +| :------- | :--------- | :------- | :------------------------------------------------- | +| `v4()` | none | - | Random UUID (version 4). | +| `v6()` | none | - | Time-ordered UUID (version 6). | +| `v7()` | none | - | Time-ordered UUID with Unix timestamp (version 7). | -**Returns:** `string` — UUID in form `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`. Throws on generation failure. +**Returns:** `string` - UUID in form `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`. Throws on generation failure. ## Examples From ed6e83960b3c29be9aa0d0b5b6d973e0361768c1 Mon Sep 17 00:00:00 2001 From: Binaek Sarkar Date: Sat, 21 Feb 2026 00:27:17 +0530 Subject: [PATCH 03/21] Refactor documentation by removing redundant headers for improved clarity --- src/content/docs/cli-reference/exec.md | 1 - src/content/docs/cli-reference/index.md | 2 -- src/content/docs/cli-reference/serve.md | 1 - src/content/docs/cli-reference/validate.md | 1 - src/content/docs/deployment-operations/cli-reference.md | 1 - src/content/docs/deployment-operations/running-as-service.md | 1 - .../docs/extensibility/writing-custom-typescript-modules.md | 1 - src/content/docs/getting-started.md | 2 -- src/content/docs/getting-started/introduction.md | 1 - src/content/docs/getting-started/quick-start.md | 2 -- .../docs/language-concepts/pattern-matching-conditionals.md | 1 - src/content/docs/language-concepts/policy-composition.md | 1 - src/content/docs/language-concepts/type-system-shapes.md | 1 - src/content/docs/reference/arithmetic-operations.md | 1 - src/content/docs/reference/boolean-operations.md | 1 - src/content/docs/reference/collection-operations.md | 1 - src/content/docs/reference/constraints.md | 1 - src/content/docs/reference/exporting-and-importing-rules.md | 1 - src/content/docs/reference/facts.md | 1 - src/content/docs/reference/functions.md | 1 - src/content/docs/reference/index.md | 1 - src/content/docs/reference/let.md | 1 - src/content/docs/reference/namespaces.md | 1 - src/content/docs/reference/policies.md | 1 - src/content/docs/reference/precedence.md | 1 - src/content/docs/reference/rules.md | 1 - src/content/docs/reference/security-and-permissions.md | 1 - src/content/docs/reference/shapes.md | 1 - src/content/docs/reference/trinary.md | 1 - src/content/docs/reference/types-and-values.md | 1 - src/content/docs/reference/typescript_modules/index.md | 1 - src/content/docs/reference/typescript_modules/sentrie/json.md | 1 - src/content/docs/reference/typescript_modules/sentrie/uuid.md | 1 - src/content/docs/typescript-modules.md | 2 -- 34 files changed, 38 deletions(-) diff --git a/src/content/docs/cli-reference/exec.md b/src/content/docs/cli-reference/exec.md index 316f177..812738a 100644 --- a/src/content/docs/cli-reference/exec.md +++ b/src/content/docs/cli-reference/exec.md @@ -3,7 +3,6 @@ title: "exec Command" description: "Execute a policy or rule with Sentrie." --- -# exec Command The `exec` command executes a policy or rule from a policy pack. This is the primary way to test and run your policies locally. diff --git a/src/content/docs/cli-reference/index.md b/src/content/docs/cli-reference/index.md index aad85a5..37b22c6 100644 --- a/src/content/docs/cli-reference/index.md +++ b/src/content/docs/cli-reference/index.md @@ -3,8 +3,6 @@ title: "CLI Reference" description: "Complete reference for the Sentrie command-line interface." --- -# CLI Reference - This document provides comprehensive reference for the Sentrie command-line interface. ## Overview diff --git a/src/content/docs/cli-reference/serve.md b/src/content/docs/cli-reference/serve.md index 8be6385..d5bb061 100644 --- a/src/content/docs/cli-reference/serve.md +++ b/src/content/docs/cli-reference/serve.md @@ -3,7 +3,6 @@ title: "serve Command" description: "Start the Sentrie HTTP server to evaluate policies." --- -# serve Command The `serve` command starts the Sentrie HTTP server to evaluate policies. diff --git a/src/content/docs/cli-reference/validate.md b/src/content/docs/cli-reference/validate.md index 6ea0554..041017a 100644 --- a/src/content/docs/cli-reference/validate.md +++ b/src/content/docs/cli-reference/validate.md @@ -3,7 +3,6 @@ title: "validate Command" description: "Validate a policy pack and its structure." --- -# validate Command The `validate` command validates a policy pack's structure, syntax, and type correctness without executing policies. This is useful for checking that your policies are correctly formatted and can be loaded before deployment. diff --git a/src/content/docs/deployment-operations/cli-reference.md b/src/content/docs/deployment-operations/cli-reference.md index 3cccd99..1046645 100644 --- a/src/content/docs/deployment-operations/cli-reference.md +++ b/src/content/docs/deployment-operations/cli-reference.md @@ -3,7 +3,6 @@ title: CLI Reference description: "Sentrie CLI: exec, init, serve, validate. Syntax, options, and behavior." --- -# CLI Reference The Sentrie CLI provides commands to execute policies, initialize packs, serve an HTTP API, and validate packs. All commands support `--help`, `--version`, `--debug`, `--log-level`. diff --git a/src/content/docs/deployment-operations/running-as-service.md b/src/content/docs/deployment-operations/running-as-service.md index a1e72ad..f0c5dbd 100644 --- a/src/content/docs/deployment-operations/running-as-service.md +++ b/src/content/docs/deployment-operations/running-as-service.md @@ -3,7 +3,6 @@ title: Running as a Service description: "HTTP API for policy evaluation: endpoints, request/response schemas, and error format." --- -# Running as a Service Start the server with `sentrie serve`. The API exposes health and decision evaluation. Request/response are JSON. Errors use RFC 9457 Problem Details. diff --git a/src/content/docs/extensibility/writing-custom-typescript-modules.md b/src/content/docs/extensibility/writing-custom-typescript-modules.md index ab689e3..2791291 100644 --- a/src/content/docs/extensibility/writing-custom-typescript-modules.md +++ b/src/content/docs/extensibility/writing-custom-typescript-modules.md @@ -3,7 +3,6 @@ title: Writing Custom TypeScript Modules description: "How to add and use your own TypeScript modules in a policy pack: paths, exports, and use statement." --- -# Writing Custom TypeScript Modules You can add TypeScript files to your policy pack and import them in policies with the `use` statement. Paths are relative to the policy file or use `@local` (pack root). Built-in modules are `@sentrie/*`; local files use quoted relative paths. diff --git a/src/content/docs/getting-started.md b/src/content/docs/getting-started.md index 4474823..20d8e34 100644 --- a/src/content/docs/getting-started.md +++ b/src/content/docs/getting-started.md @@ -3,8 +3,6 @@ title: "Getting Started" description: "Learn the basics of Sentrie and write your first policy." --- -# Getting Started - Welcome to Sentrie! This guide will help you get up and running with Sentrie quickly. ## What is Sentrie? diff --git a/src/content/docs/getting-started/introduction.md b/src/content/docs/getting-started/introduction.md index 5ce6aae..c81378d 100644 --- a/src/content/docs/getting-started/introduction.md +++ b/src/content/docs/getting-started/introduction.md @@ -3,7 +3,6 @@ title: Introduction & Core Philosophy description: Sentrie is a policy engine with deterministic evaluation. This page states what Sentrie is and why determinism is central. --- -# Introduction & Core Philosophy Sentrie is an open-source policy engine. You write business rules in a dedicated language; the engine evaluates them with deterministic, bounded execution. Use it when you need consistent policy decisions across services, auditability, and predictable performance. diff --git a/src/content/docs/getting-started/quick-start.md b/src/content/docs/getting-started/quick-start.md index 1f76516..b2831f1 100644 --- a/src/content/docs/getting-started/quick-start.md +++ b/src/content/docs/getting-started/quick-start.md @@ -3,8 +3,6 @@ title: Quick Start description: Install Sentrie and run your first policy evaluation. --- -# Quick Start - Install the Sentrie binary and evaluate a policy from the command line. This page covers installation and a minimal run (no policy pack layout). ## Syntax diff --git a/src/content/docs/language-concepts/pattern-matching-conditionals.md b/src/content/docs/language-concepts/pattern-matching-conditionals.md index 33fba86..b458b7b 100644 --- a/src/content/docs/language-concepts/pattern-matching-conditionals.md +++ b/src/content/docs/language-concepts/pattern-matching-conditionals.md @@ -3,7 +3,6 @@ title: Pattern Matching & Conditionals description: How conditional selection (ternary, Elvis), pattern matching (matches), and state checks work in Sentrie. --- -# Pattern Matching & Conditionals Sentrie uses trinary logic (`true`, `false`, `unknown`) and provides the ternary and Elvis operators for conditional values, plus `matches` for regex and `is defined` / `is empty` for state checks. This page describes how these work. diff --git a/src/content/docs/language-concepts/policy-composition.md b/src/content/docs/language-concepts/policy-composition.md index 86a74de..a721fe0 100644 --- a/src/content/docs/language-concepts/policy-composition.md +++ b/src/content/docs/language-concepts/policy-composition.md @@ -3,7 +3,6 @@ title: Policy Composition description: "How exporting and importing rules work: syntax, attachments, sandboxing, and namespace resolution." --- -# Policy Composition Policies export rules so they can be executed via CLI/API or imported by other policies. Importing a rule runs it in isolation with only the facts you supply via `with` clauses. This page describes how export and import work. diff --git a/src/content/docs/language-concepts/type-system-shapes.md b/src/content/docs/language-concepts/type-system-shapes.md index 853798e..545d188 100644 --- a/src/content/docs/language-concepts/type-system-shapes.md +++ b/src/content/docs/language-concepts/type-system-shapes.md @@ -3,7 +3,6 @@ title: Type System & Shapes Overview description: "How types, shapes, and constraints work in Sentrie: structure, validation, and composition." --- -# Type System & Shapes Overview The type system defines values (primitives, collections, documents) and structured data (shapes). Constraints validate values at runtime. Understanding how types and shapes work is required to define facts and rules correctly. diff --git a/src/content/docs/reference/arithmetic-operations.md b/src/content/docs/reference/arithmetic-operations.md index 915bee1..e4ac246 100644 --- a/src/content/docs/reference/arithmetic-operations.md +++ b/src/content/docs/reference/arithmetic-operations.md @@ -3,7 +3,6 @@ title: Arithmetic Operations description: "Arithmetic operators: +, -, *, /, %; types and edge cases." --- -# Arithmetic Operations Arithmetic operators operate on `number` (float64). All numeric operands are unified as `number`. Result type is `number`. diff --git a/src/content/docs/reference/boolean-operations.md b/src/content/docs/reference/boolean-operations.md index a4b8d06..982e355 100644 --- a/src/content/docs/reference/boolean-operations.md +++ b/src/content/docs/reference/boolean-operations.md @@ -3,7 +3,6 @@ title: Boolean Operations description: Logical (and, or, xor, not) and comparison (==, !=, <, <=, >, >=) operators. --- -# Boolean Operations Logical and comparison operators produce trinary or bool results. Operands are trinary; comparisons and logical ops follow [trinary](/reference/trinary) semantics. diff --git a/src/content/docs/reference/collection-operations.md b/src/content/docs/reference/collection-operations.md index a3e27b1..458a66b 100644 --- a/src/content/docs/reference/collection-operations.md +++ b/src/content/docs/reference/collection-operations.md @@ -3,7 +3,6 @@ title: Collection Operations description: "Quantifiers and transformers: any, all, filter, map, reduce, count, distinct." --- -# Collection Operations Collection operations apply to lists and maps. They are declarative: they return new values or collections and do not mutate the input. Syntax uses a block with `yield`. diff --git a/src/content/docs/reference/constraints.md b/src/content/docs/reference/constraints.md index d81ada2..17a6975 100644 --- a/src/content/docs/reference/constraints.md +++ b/src/content/docs/reference/constraints.md @@ -3,7 +3,6 @@ title: Constraints description: Constraint syntax and validation for types (e.g. @min, @max, @email). --- -# Constraints Constraints validate values at runtime using the `@` syntax on types. They apply to primitives, collection elements, and shape fields. Validation failure aborts evaluation. diff --git a/src/content/docs/reference/exporting-and-importing-rules.md b/src/content/docs/reference/exporting-and-importing-rules.md index f4dbda3..6590efb 100644 --- a/src/content/docs/reference/exporting-and-importing-rules.md +++ b/src/content/docs/reference/exporting-and-importing-rules.md @@ -3,7 +3,6 @@ title: "Exporting and Importing Rules" description: "Learn how to export rules from policies and import them into other policies for code reuse and composition." --- -# Exporting and Importing Rules Sentrie allows you to export rules from one policy and import them into another, enabling code reuse and policy composition. This guide covers how to export rules, import them, and work with attachments. diff --git a/src/content/docs/reference/facts.md b/src/content/docs/reference/facts.md index 8337ee1..03f34ad 100644 --- a/src/content/docs/reference/facts.md +++ b/src/content/docs/reference/facts.md @@ -3,7 +3,6 @@ title: Facts description: "Fact declaration syntax: required/optional, type, alias, default." --- -# Facts Facts are named inputs to a policy. They are declared at the top of the policy (after comments and other facts). Required by default; use `?` for optional. Only optional facts may have a default. Facts are non-nullable. diff --git a/src/content/docs/reference/functions.md b/src/content/docs/reference/functions.md index 08c7dbb..06eea60 100644 --- a/src/content/docs/reference/functions.md +++ b/src/content/docs/reference/functions.md @@ -3,7 +3,6 @@ title: Functions description: Function call syntax and TypeScript module usage; memoization. --- -# Functions Functions are called with `name(args...)` or `alias.name(args...)` for imported modules. Sentrie has no built-in global functions; all functions come from TypeScript modules imported with `use`. diff --git a/src/content/docs/reference/index.md b/src/content/docs/reference/index.md index cb9921f..89b09c5 100644 --- a/src/content/docs/reference/index.md +++ b/src/content/docs/reference/index.md @@ -3,7 +3,6 @@ title: Policy Language Reference description: Exhaustive dictionary of Sentrie language syntax and features. --- -# Policy Language Reference This section is a strict reference for the Sentrie policy language: syntax, types, operators, and constructs. For conceptual overviews, see [Language Concepts](/language-concepts/type-system-shapes). diff --git a/src/content/docs/reference/let.md b/src/content/docs/reference/let.md index 8312f6f..d1f9b56 100644 --- a/src/content/docs/reference/let.md +++ b/src/content/docs/reference/let.md @@ -3,7 +3,6 @@ title: Intermediate Values (let) description: let declaration syntax, scoping, and immutability. --- -# Intermediate Values (let) `let` binds a name to an expression inside a block. It is scoped to that block, immutable, and cannot be exported. Used for intermediate calculations in policies and rules. diff --git a/src/content/docs/reference/namespaces.md b/src/content/docs/reference/namespaces.md index 6055f19..5b454b3 100644 --- a/src/content/docs/reference/namespaces.md +++ b/src/content/docs/reference/namespaces.md @@ -3,7 +3,6 @@ title: Namespaces description: Namespace syntax and rules for organizing policies and shapes. --- -# Namespaces Namespaces group policies and shapes and form the visibility boundary for unexported shapes. Each `*.sentrie` file has exactly one namespace and it must be the first statement. diff --git a/src/content/docs/reference/policies.md b/src/content/docs/reference/policies.md index b28ce76..e6bfa29 100644 --- a/src/content/docs/reference/policies.md +++ b/src/content/docs/reference/policies.md @@ -3,7 +3,6 @@ title: Policies description: "Policy syntax and allowed statements: facts, rules, let, use, export." --- -# Policies A policy is a named block inside a namespace. It contains facts (inputs), rules (decisions), optional `let` bindings and `use` statements, and at least one `export decision of` rule. diff --git a/src/content/docs/reference/precedence.md b/src/content/docs/reference/precedence.md index 20886f0..8f1ff2c 100644 --- a/src/content/docs/reference/precedence.md +++ b/src/content/docs/reference/precedence.md @@ -3,7 +3,6 @@ title: Precedence description: Operator precedence (highest to lowest) for expressions. --- -# Precedence Operators are evaluated in order of precedence (highest first). Same precedence is left-to-right unless stated otherwise. Use parentheses to override. diff --git a/src/content/docs/reference/rules.md b/src/content/docs/reference/rules.md index 765928f..9dbe9f4 100644 --- a/src/content/docs/reference/rules.md +++ b/src/content/docs/reference/rules.md @@ -3,7 +3,6 @@ title: Rules description: Rule syntax, evaluation (when/default/body), and outcome (trinary or value). --- -# Rules A rule defines a decision: an optional `when` condition, an optional `default`, and a body that must contain `yield`. If `when` is truthy the body is evaluated; otherwise the `default` (or `unknown`) is used. diff --git a/src/content/docs/reference/security-and-permissions.md b/src/content/docs/reference/security-and-permissions.md index 13ec894..dadea0f 100644 --- a/src/content/docs/reference/security-and-permissions.md +++ b/src/content/docs/reference/security-and-permissions.md @@ -3,7 +3,6 @@ title: Security and Permissions description: "Policy pack permissions: filesystem, network, environment." --- -# Security and Permissions Permissions define what a policy pack can access: filesystem (read), network hosts, and environment variables. They are set in `sentrie.pack.toml` under `[permissions]`. Default is pack-root filesystem only; no network; no env. diff --git a/src/content/docs/reference/shapes.md b/src/content/docs/reference/shapes.md index 6061c3d..bf17356 100644 --- a/src/content/docs/reference/shapes.md +++ b/src/content/docs/reference/shapes.md @@ -3,7 +3,6 @@ title: Shapes description: "Shape syntax: data models, field modifiers, composition, and type aliases." --- -# Shapes Shapes define structured data (fields) or type aliases (base type + constraints). They are used for facts and `let` bindings. Optional fields use `?`; required non-null use `!`. Composition uses `with Base`. diff --git a/src/content/docs/reference/trinary.md b/src/content/docs/reference/trinary.md index a9392aa..0c31998 100644 --- a/src/content/docs/reference/trinary.md +++ b/src/content/docs/reference/trinary.md @@ -3,7 +3,6 @@ title: Trinary Values description: "Three-valued logic: true, false, unknown; truthiness and Kleene tables." --- -# Trinary Values Sentrie uses trinary logic: `true`, `false`, and `unknown`. Unknown represents indeterminate truth (e.g. undefined field access). Logical operators follow Kleene's three-valued logic. diff --git a/src/content/docs/reference/types-and-values.md b/src/content/docs/reference/types-and-values.md index 72936a5..e92820f 100644 --- a/src/content/docs/reference/types-and-values.md +++ b/src/content/docs/reference/types-and-values.md @@ -3,7 +3,6 @@ title: Types and Values description: Built-in primitive and collection types and type declarations. --- -# Types and Values Sentrie provides primitive types (`number`, `string`, `trinary`, `bool`, `document`) and collection types (`list[T]`, `map[T]`, `record[T1,T2,...]`). User-defined shapes extend these. Types can be used in `let`, `fact`, and shape fields. diff --git a/src/content/docs/reference/typescript_modules/index.md b/src/content/docs/reference/typescript_modules/index.md index 3751f1c..dfd30b0 100644 --- a/src/content/docs/reference/typescript_modules/index.md +++ b/src/content/docs/reference/typescript_modules/index.md @@ -3,7 +3,6 @@ title: Built-in TypeScript Modules description: "Reference for built-in @sentrie/* modules: import syntax and module list." --- -# Built-in TypeScript Modules Built-in modules provide functions for hashing, encoding, time, JSON, regex, and more. Import with `use { ... } from @sentrie/module`; no quotes. For custom modules see [Writing custom TypeScript modules](/extensibility/writing-custom-typescript-modules). diff --git a/src/content/docs/reference/typescript_modules/sentrie/json.md b/src/content/docs/reference/typescript_modules/sentrie/json.md index ccc1857..ac4c66e 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/json.md +++ b/src/content/docs/reference/typescript_modules/sentrie/json.md @@ -3,7 +3,6 @@ title: "@sentrie/json" description: JSON validation. For parse/stringify use @sentrie/js. --- -# @sentrie/json Provides `isValid` to check if a string is valid JSON. For parsing and stringifying use [@sentrie/js](/reference/typescript_modules/sentrie/js). diff --git a/src/content/docs/reference/typescript_modules/sentrie/uuid.md b/src/content/docs/reference/typescript_modules/sentrie/uuid.md index 5222598..ecd96ba 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/uuid.md +++ b/src/content/docs/reference/typescript_modules/sentrie/uuid.md @@ -3,7 +3,6 @@ title: "@sentrie/uuid" description: UUID generation (v4, v6, v7). --- -# @sentrie/uuid Generates UUIDs. v4: random; v6/v7: time-ordered. Use when you need unique identifiers or time-ordered IDs for indexing. diff --git a/src/content/docs/typescript-modules.md b/src/content/docs/typescript-modules.md index b7067c8..8fe53ad 100644 --- a/src/content/docs/typescript-modules.md +++ b/src/content/docs/typescript-modules.md @@ -3,8 +3,6 @@ title: "JavaScript Modules" description: "Integration with JavaScript modules for complex business logic." --- -# JavaScript Modules - > **Note**: This feature is currently under development and will be available in a future release. ## Overview From 34229fc570661e703c995b40ef0aecf908dfe017 Mon Sep 17 00:00:00 2001 From: Binaek Sarkar Date: Sat, 21 Feb 2026 16:25:58 +0530 Subject: [PATCH 04/21] Update documentation to replace "Parameters" headers with "Concepts" for consistency across multiple files --- docs-template.md | 2 +- src/content/docs/deployment-operations/cli-reference.md | 2 +- src/content/docs/deployment-operations/running-as-service.md | 2 +- .../docs/extensibility/writing-custom-typescript-modules.md | 2 +- src/content/docs/getting-started/quick-start.md | 2 +- .../docs/language-concepts/pattern-matching-conditionals.md | 2 +- src/content/docs/language-concepts/policy-composition.md | 2 +- src/content/docs/language-concepts/type-system-shapes.md | 2 +- src/content/docs/reference/arithmetic-operations.md | 2 +- src/content/docs/reference/boolean-operations.md | 2 +- src/content/docs/reference/collection-operations.md | 2 +- src/content/docs/reference/constraints.md | 2 +- src/content/docs/reference/facts.md | 2 +- src/content/docs/reference/functions.md | 2 +- src/content/docs/reference/let.md | 2 +- src/content/docs/reference/namespaces.md | 2 +- src/content/docs/reference/policies.md | 2 +- src/content/docs/reference/precedence.md | 2 +- src/content/docs/reference/rules.md | 2 +- src/content/docs/reference/security-and-permissions.md | 2 +- src/content/docs/reference/shapes.md | 2 +- src/content/docs/reference/trinary.md | 2 +- src/content/docs/reference/types-and-values.md | 2 +- src/content/docs/reference/typescript_modules/index.md | 2 +- src/content/docs/reference/typescript_modules/sentrie/json.md | 2 +- src/content/docs/reference/typescript_modules/sentrie/uuid.md | 2 +- 26 files changed, 26 insertions(+), 26 deletions(-) diff --git a/docs-template.md b/docs-template.md index 8dfb1ae..b763460 100644 --- a/docs-template.md +++ b/docs-template.md @@ -8,7 +8,7 @@ // The formal definition or type signature ``` -## Parameters +## Concepts | Name | Type | Required | Description | | :---------- | :----- | :------- | :---------------------------------------------------- | diff --git a/src/content/docs/deployment-operations/cli-reference.md b/src/content/docs/deployment-operations/cli-reference.md index 1046645..ced4103 100644 --- a/src/content/docs/deployment-operations/cli-reference.md +++ b/src/content/docs/deployment-operations/cli-reference.md @@ -15,7 +15,7 @@ sentrie serve [OPTIONS] [policy-pack] sentrie validate [OPTIONS] ``` -## Parameters +## Concepts | Command | Required | Description | | :--- | :--- | :--- | diff --git a/src/content/docs/deployment-operations/running-as-service.md b/src/content/docs/deployment-operations/running-as-service.md index f0c5dbd..ab4bf87 100644 --- a/src/content/docs/deployment-operations/running-as-service.md +++ b/src/content/docs/deployment-operations/running-as-service.md @@ -73,7 +73,7 @@ Evaluates a policy or rule. `{target...}` = path segments: `namespace/policy` or | `attachments` | object | Exported attachments for that rule. | | `error` | string | Non-empty if execution failed. | -## Parameters +## Concepts | Endpoint | Method | Path | Body | | :--- | :--- | :--- | :--- | diff --git a/src/content/docs/extensibility/writing-custom-typescript-modules.md b/src/content/docs/extensibility/writing-custom-typescript-modules.md index 2791291..dc33879 100644 --- a/src/content/docs/extensibility/writing-custom-typescript-modules.md +++ b/src/content/docs/extensibility/writing-custom-typescript-modules.md @@ -15,7 +15,7 @@ use { fn1 } from "@local/path/to/module" [ as alias ] Built-in: `use { fn1 } from @sentrie/module` (no quotes). Local: quoted path or `@local/...`; resolved relative to current file or pack root. -## Parameters +## Concepts | Element | Type | Required | Description | | :--- | :--- | :--- | :--- | diff --git a/src/content/docs/getting-started/quick-start.md b/src/content/docs/getting-started/quick-start.md index b2831f1..5f74bba 100644 --- a/src/content/docs/getting-started/quick-start.md +++ b/src/content/docs/getting-started/quick-start.md @@ -27,7 +27,7 @@ Verify: sentrie --version ``` -## Parameters +## Concepts | Step | Required | Description | | :--- | :--- | :--- | diff --git a/src/content/docs/language-concepts/pattern-matching-conditionals.md b/src/content/docs/language-concepts/pattern-matching-conditionals.md index b458b7b..868f001 100644 --- a/src/content/docs/language-concepts/pattern-matching-conditionals.md +++ b/src/content/docs/language-concepts/pattern-matching-conditionals.md @@ -18,7 +18,7 @@ Sentrie uses trinary logic (`true`, `false`, `unknown`) and provides the ternary **Emptiness:** `value is empty` | `value is not empty` -## Parameters +## Concepts | Construct | Left | Right | Description | | :----------- | :------------------ | :-------------------- | :---------------------------------------------------------------- | diff --git a/src/content/docs/language-concepts/policy-composition.md b/src/content/docs/language-concepts/policy-composition.md index a721fe0..76fb705 100644 --- a/src/content/docs/language-concepts/policy-composition.md +++ b/src/content/docs/language-concepts/policy-composition.md @@ -24,7 +24,7 @@ rule localName = import decision of ruleName with targetFact2 as expression2 ``` -## Parameters +## Concepts | Element | Type | Required | Description | | :--- | :--- | :--- | :--- | diff --git a/src/content/docs/language-concepts/type-system-shapes.md b/src/content/docs/language-concepts/type-system-shapes.md index 545d188..341d961 100644 --- a/src/content/docs/language-concepts/type-system-shapes.md +++ b/src/content/docs/language-concepts/type-system-shapes.md @@ -28,7 +28,7 @@ shape Name { **Constraints:** Applied with `@` on types (e.g. `number @min(0) @max(100)`). -## Parameters +## Concepts | Concept | Required | Description | | :---------- | :------- | :----------------------------------------------------- | diff --git a/src/content/docs/reference/arithmetic-operations.md b/src/content/docs/reference/arithmetic-operations.md index e4ac246..dbdcff9 100644 --- a/src/content/docs/reference/arithmetic-operations.md +++ b/src/content/docs/reference/arithmetic-operations.md @@ -18,7 +18,7 @@ expr % expr Unary: `+ expr` | `- expr` -## Parameters +## Concepts | Operator | Description | Result | | :--- | :--- | :--- | diff --git a/src/content/docs/reference/boolean-operations.md b/src/content/docs/reference/boolean-operations.md index 982e355..3d902ea 100644 --- a/src/content/docs/reference/boolean-operations.md +++ b/src/content/docs/reference/boolean-operations.md @@ -14,7 +14,7 @@ Logical and comparison operators produce trinary or bool results. Operands are t **Conditional:** `condition ? trueValue : falseValue` | `expr ?: default` -## Parameters +## Concepts | Operator | Description | Returns | | :--- | :--- | :--- | diff --git a/src/content/docs/reference/collection-operations.md b/src/content/docs/reference/collection-operations.md index 458a66b..061e9b2 100644 --- a/src/content/docs/reference/collection-operations.md +++ b/src/content/docs/reference/collection-operations.md @@ -20,7 +20,7 @@ distinct collection Index parameter is optional in some forms. For maps, element is key-value or value depending on operation. -## Parameters +## Concepts | Operation | Input | Output | Description | | :--- | :--- | :--- | :--- | diff --git a/src/content/docs/reference/constraints.md b/src/content/docs/reference/constraints.md index 17a6975..396e3d4 100644 --- a/src/content/docs/reference/constraints.md +++ b/src/content/docs/reference/constraints.md @@ -14,7 +14,7 @@ type @constraint1(args) @constraint2 Examples: `number @min(0) @max(100)`, `string @email`, `list[string @one_of("a","b")]`. -## Parameters +## Concepts | Category | Constraints (examples) | | :--- | :--- | diff --git a/src/content/docs/reference/facts.md b/src/content/docs/reference/facts.md index 03f34ad..902839f 100644 --- a/src/content/docs/reference/facts.md +++ b/src/content/docs/reference/facts.md @@ -13,7 +13,7 @@ fact name : type [ as alias ] [ default expr ] -- required fact name? : type [ as alias ] [ default expr ] -- optional ``` -## Parameters +## Concepts | Part | Type | Required | Description | | :------------- | :-------------- | :------- | :-------------------------------------------------- | diff --git a/src/content/docs/reference/functions.md b/src/content/docs/reference/functions.md index 06eea60..2404008 100644 --- a/src/content/docs/reference/functions.md +++ b/src/content/docs/reference/functions.md @@ -15,7 +15,7 @@ alias.functionName(arg1, arg2, ...) Import: `use { fn1, fn2 } from source [ as alias ]` -## Parameters +## Concepts | Element | Type | Required | Description | | :--- | :--- | :--- | :--- | diff --git a/src/content/docs/reference/let.md b/src/content/docs/reference/let.md index d1f9b56..8ca445f 100644 --- a/src/content/docs/reference/let.md +++ b/src/content/docs/reference/let.md @@ -13,7 +13,7 @@ let name = expr let name : type = expr ``` -## Parameters +## Concepts | Part | Type | Required | Description | | :--- | :--- | :--- | :--- | diff --git a/src/content/docs/reference/namespaces.md b/src/content/docs/reference/namespaces.md index 5b454b3..4b6c57b 100644 --- a/src/content/docs/reference/namespaces.md +++ b/src/content/docs/reference/namespaces.md @@ -14,7 +14,7 @@ namespace fully/qualified/name FQN is slash-separated identifiers (e.g. `com/example/auth`, `mycompany/policies/security`). -## Parameters +## Concepts | Element | Type | Required | Description | | :--- | :--- | :--- | :--- | diff --git a/src/content/docs/reference/policies.md b/src/content/docs/reference/policies.md index e6bfa29..0098766 100644 --- a/src/content/docs/reference/policies.md +++ b/src/content/docs/reference/policies.md @@ -18,7 +18,7 @@ policy IDENT { } ``` -## Parameters +## Concepts | Statement | Required | Description | | :--- | :--- | :--- | diff --git a/src/content/docs/reference/precedence.md b/src/content/docs/reference/precedence.md index 8f1ff2c..d9c72a6 100644 --- a/src/content/docs/reference/precedence.md +++ b/src/content/docs/reference/precedence.md @@ -10,7 +10,7 @@ Operators are evaluated in order of precedence (highest first). Same precedence Expressions combine operators; precedence determines binding. Ternary `? :` is right-associative. -## Parameters +## Concepts | Precedence | Operators | Description | | :--- | :--- | :--- | diff --git a/src/content/docs/reference/rules.md b/src/content/docs/reference/rules.md index 9dbe9f4..e0ed5dc 100644 --- a/src/content/docs/reference/rules.md +++ b/src/content/docs/reference/rules.md @@ -12,7 +12,7 @@ A rule defines a decision: an optional `when` condition, an optional `default`, rule IDENT = [ default expr ] [ when expr ] { stmt* yield expr } ``` -## Parameters +## Concepts | Part | Type | Required | Description | | :--- | :--- | :--- | :--- | diff --git a/src/content/docs/reference/security-and-permissions.md b/src/content/docs/reference/security-and-permissions.md index dadea0f..c13f6d4 100644 --- a/src/content/docs/reference/security-and-permissions.md +++ b/src/content/docs/reference/security-and-permissions.md @@ -15,7 +15,7 @@ net = ["host1.com", "host2.com"] env = ["VAR1", "VAR2"] ``` -## Parameters +## Concepts | Key | Type | Description | | :--- | :--- | :--- | diff --git a/src/content/docs/reference/shapes.md b/src/content/docs/reference/shapes.md index bf17356..105a4aa 100644 --- a/src/content/docs/reference/shapes.md +++ b/src/content/docs/reference/shapes.md @@ -21,7 +21,7 @@ shape Name { **Composition:** `shape Child with Base { ... }` -## Parameters +## Concepts | Modifier | Description | | :--- | :--- | diff --git a/src/content/docs/reference/trinary.md b/src/content/docs/reference/trinary.md index 0c31998..1782404 100644 --- a/src/content/docs/reference/trinary.md +++ b/src/content/docs/reference/trinary.md @@ -14,7 +14,7 @@ Logical: `and` | `or` | `xor` | `not` | `!` **Returns:** Trinary. For `when` and conditionals: only `true` is truthy; `false` and `unknown` are not. -## Parameters +## Concepts Truthiness: only `true` is truthy. `false` and `unknown` are non-truthy (e.g. for `when`, ternary, Elvis). diff --git a/src/content/docs/reference/types-and-values.md b/src/content/docs/reference/types-and-values.md index e92820f..2aed953 100644 --- a/src/content/docs/reference/types-and-values.md +++ b/src/content/docs/reference/types-and-values.md @@ -16,7 +16,7 @@ Sentrie provides primitive types (`number`, `string`, `trinary`, `bool`, `docume List index: `expr[number]`. Map index: `expr[string]` or `expr.key`. -## Parameters +## Concepts | Type | Description | | :--- | :--- | diff --git a/src/content/docs/reference/typescript_modules/index.md b/src/content/docs/reference/typescript_modules/index.md index dfd30b0..6d371d4 100644 --- a/src/content/docs/reference/typescript_modules/index.md +++ b/src/content/docs/reference/typescript_modules/index.md @@ -14,7 +14,7 @@ use { fn1, fn2 } from @sentrie/module [ as alias ] Built-in modules: `@sentrie/module` (no quotes). Default alias is the last path segment (e.g. `time` for `@sentrie/time`). -## Parameters +## Concepts | Element | Type | Required | Description | | :--- | :--- | :--- | :--- | diff --git a/src/content/docs/reference/typescript_modules/sentrie/json.md b/src/content/docs/reference/typescript_modules/sentrie/json.md index ac4c66e..e899611 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/json.md +++ b/src/content/docs/reference/typescript_modules/sentrie/json.md @@ -13,7 +13,7 @@ use { isValid } from @sentrie/json [ as alias ] alias.isValid(str) ``` -## Parameters +## Concepts | Name | Type | Required | Description | | :---- | :----- | :------- | :-------------------------- | diff --git a/src/content/docs/reference/typescript_modules/sentrie/uuid.md b/src/content/docs/reference/typescript_modules/sentrie/uuid.md index ecd96ba..fd094ad 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/uuid.md +++ b/src/content/docs/reference/typescript_modules/sentrie/uuid.md @@ -15,7 +15,7 @@ alias.v6() alias.v7() ``` -## Parameters +## Concepts | Function | Parameters | Required | Description | | :------- | :--------- | :------- | :------------------------------------------------- | From 07cdd2c6d542443b1c4ded798a7c1f0eae3f9138 Mon Sep 17 00:00:00 2001 From: Binaek Sarkar Date: Sat, 21 Feb 2026 16:50:00 +0530 Subject: [PATCH 05/21] Enhance getting started documentation with new sections for installation, writing your first policy, and running policies --- astro.config.mjs | 4 + .../docs/getting-started/enforcement.md | 93 ++++--- .../getting-started/running-your-policy.md | 111 ++++---- .../writing-your-first-policy.md | 245 +++++------------- 4 files changed, 156 insertions(+), 297 deletions(-) diff --git a/astro.config.mjs b/astro.config.mjs index 96e4a56..54b4443 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -28,6 +28,10 @@ export default defineConfig({ items: [ { label: "Introduction & Core Philosophy", slug: "getting-started/introduction" }, { label: "Quick Start", slug: "getting-started/quick-start" }, + { label: "Installation", slug: "getting-started/installation" }, + { label: "Writing your first Policy", slug: "getting-started/writing-your-first-policy" }, + { label: "Running your Policy", slug: "getting-started/running-your-policy" }, + { label: "Enforcement", slug: "getting-started/enforcement" }, ], }, { diff --git a/src/content/docs/getting-started/enforcement.md b/src/content/docs/getting-started/enforcement.md index b018276..18ec5c0 100644 --- a/src/content/docs/getting-started/enforcement.md +++ b/src/content/docs/getting-started/enforcement.md @@ -1,74 +1,71 @@ --- title: Enforcement -description: Enforcing policies using Sentrie +description: "Sentrie returns decisions only; enforcement is implemented by the caller." --- -## Enforcement is external +Sentrie is a deterministic policy engine. It evaluates facts against rules and returns decisions (e.g. boolean or trinary). It does not perform enforcement: no blocking, no API calls, no side effects. The caller uses the decision to enforce (allow/deny, redact, step-up auth, etc.). -Sentrie is a **deterministic policy decision engine**. It evaluates structured inputs against rules and returns `true`, `false`, or `unknown`. That's it. +## Syntax -The engine is **pure** and **side-effect-free**-no mutations, no API calls, no state changes. This makes it safe to embed deep in your stack: IAM systems, API gateways, control planes, multi-tenant services, anywhere you need fast, reliable authorization decisions. +**CLI:** Decisions are printed to stdout; exit code indicates success or failure. Caller script or service interprets the output and enforces. -Because enforcement lives outside the policy, the same rules work across different systems with different enforcement modes - whether you're blocking requests, redacting data, or triggering step-up auth. +**HTTP:** Caller sends a POST with facts; response body contains decision(s). Caller enforces based on the response. -:::note[Sentrie makes decisions] -Sentrie focuses on **decision quality** - your systems handle enforcement. -::: +```text +POST /decision/{namespace}/{policy}/{rule} +Content-Type: application/json -## Why Enforcement is External +Request body: { "facts": { "factName": value, ... } } -Different platforms enforce differently: +Response: decision value(s); format depends on endpoint. +``` -- **IAM layers** allow, deny, or escalate auth flows -- **API gateways** throttle, rate-limit, or block routes -- **Multi-tenant systems** control features and per-org limits -- **Security layers** trigger reviews or step-up verification -- **Backend services** serve, redact, or transform data +## Concepts -Keeping the engine pure gives you three guarantees: +| Concept | Description | +| :--- | :--- | +| Decision | Output of a rule: typically `true`, `false`, or `unknown` (trinary). Sentrie only returns this; it does not act on it. | +| Enforcement | Action taken by the caller: allow/deny request, redact data, trigger step-up auth, throttle, etc. Implemented in IAM, API gateway, backend, or feature flag system. | +| Determinism | Same facts and policy → same decision. No mutable state or side effects inside the engine. Safe to replay and audit. | +| Portability | Same policy runs identically via CLI or HTTP; enforcement logic lives in the integrating system. | -- **Determinism** - same input = same output, always -- **Reproducibility** - decisions are safe to replay and audit -- **Portability** - runs identically everywhere +**Returns:** Sentrie returns decision value(s). Exit code (CLI) or HTTP status indicates success/failure. No enforcement is performed by Sentrie. -This is what makes Sentrie safe for **real-time**, **latency-sensitive** paths. +## Examples -## How It Works +### CLI: caller enforces from exit code and stdout -Call Sentrie's HTTP endpoint with context. Get a decision. **Enforce it**. +```bash +result=$(sentrie exec com/example/auth/access/allow --facts '{"user": {"role": "user"}}') +# Parse $result; if allow is true, proceed; else deny or redirect. +``` + +### HTTP: caller enforces from response body ```bash -curl -X POST https://sentrie.host:7529/decision/namespace/policy/rule \ +curl -s -X POST "https://sentrie.host:7529/decision/com/example/auth/access/allow" \ -H "Content-Type: application/json" \ - -d '{ - "facts": { - "user": "...", - "org": "...", - "roles": ["..."], - "plan": "pro" - } - }' + -d '{"facts": {"user": {"role": "admin", "status": "active"}}}' ``` -What happens next is up to you: +Caller reads the response; if decision is allow, the IAM/gateway/backend allows the request; otherwise it denies, redacts, or escalates. + +### Typical enforcement roles -- Backends accept, reject, or redact responses -- IAM systems allow, deny, or trigger step-up auth -- Gateways block, throttle, or downgrade routes -- Feature gates enable, disable, or shadow-test features -- Entitlement systems enforce quotas or usage limits -- Data layers redact fields before returning them +- **IAM / Auth:** Allow, deny, or trigger step-up based on decision. +- **API gateway:** Allow, throttle, or block route based on decision. +- **Backend:** Serve full response, redact fields, or return 403 based on decision. +- **Feature / entitlement:** Enable, disable, or cap usage based on decision. -:::note[Philosophy] -Sentrie supplies truth. Systems apply consequences. -::: +## Behavior & Constraints -## Why This Works +- **No side effects:** Sentrie does not mutate state, call external APIs, or perform enforcement. It only evaluates and returns decisions. +- **Caller responsibility:** The system that calls Sentrie (CLI script, API gateway, backend service) must interpret the decision and enforce (allow/deny, redact, etc.). +- **Determinism:** Same inputs and policy produce the same decision. Safe for critical paths and replay. +- **Same policy, many enforcers:** One policy can be used by IAM, gateway, and backend; each enforces in its own way. -Separating decision from action makes Sentrie: +## Constraints & Edge Cases -- Safe for critical paths -- Consistent across systems -- Easy to test and replay -- Independently scalable -- Flexible for different enforcement modes +- Sentrie does not enforce. Missing or incorrect enforcement is a caller bug, not an engine behavior. +- Timeouts, network errors, and auth to the Sentrie endpoint are the caller’s responsibility. +- For HTTP, see Deployment & Operations (e.g. Running as a Service) for exact request/response schema and status codes. diff --git a/src/content/docs/getting-started/running-your-policy.md b/src/content/docs/getting-started/running-your-policy.md index f7ca68c..60a00b9 100644 --- a/src/content/docs/getting-started/running-your-policy.md +++ b/src/content/docs/getting-started/running-your-policy.md @@ -1,105 +1,92 @@ --- title: Running your first Policy -description: Learn how to run the policy you created using the exec command +description: "Evaluate policies with sentrie exec: target, facts JSON, and output format." --- -Now that you've created your first policy in the [Writing your first Policy](/getting-started/writing-your-first-policy/) guide, let's run it using the `sentrie exec` command. +Use `sentrie exec` to evaluate one or all exported rules in a policy. You supply a target (`namespace/policy` or `namespace/policy/rule`) and a JSON object of facts. The CLI returns rule results and exit code. -## Your Policy +## Syntax -Assuming you followed the previous guide, you should have a policy file `first-policy.sentrie` that looks like this: - -```sentrie -// first-policy.sentrie -namespace user_management - -shape User { - role: string - status: string -} - -policy user_access { +```bash +sentrie exec TARGET [ --facts JSON ] [ --pack PATH ] +``` - fact user: User +- **TARGET:** `namespace/policy` (all exported rules) or `namespace/policy/rule` (single rule). Run from pack directory or use `--pack PATH`. +- **Facts:** JSON object. Keys are fact names (or aliases). Required facts must be present; optional facts may be omitted if they have defaults. - rule allow_admin = { - yield user.role == "admin" - } +## Concepts - rule allow_user = { - yield allow_admin or (user.role == "user" and user.status == "active") - } +| Concept | Required | Description | +| :--- | :--- | :--- | +| Target | Yes | `namespace/policy` or `namespace/policy/rule`. Slashes match namespace and policy/rule identifiers. | +| Facts | Depends | JSON object. Required if the policy declares required facts. Keys match fact names (or aliases). | +| Pack path | No | Default: current directory. Use `--pack PATH` to point at a different pack root. | - export decision of allow_admin - export decision of allow_user +**Returns:** Exit code 0 on success; non-zero on evaluation or CLI error. Rule names and decision values are printed to stdout. Output includes namespace, policy, rules (match and value), and optional attachments. -} -``` +## Examples -## Running the Policy +### Evaluate a single rule -Use the `sentrie exec` command to run your policy against test data: +Policy has namespace `com/example/user_management`, policy `user_access`, exported rule `allow_user`. Required fact: `user` (shape `User` with `role`, `status`). ```bash -sentrie exec user_management/user_access/allow_user --facts '{"user": {"role": "user", "status": "active"}}' +sentrie exec com/example/user_management/user_access/allow_user --facts '{"user": {"role": "user", "status": "active"}}' ``` -:::note -If no rule name is provided, the executor will evaluate all exported rules and return the results. -::: +Example stdout: -**Expected Output:** - -``` -Namespace: user_management +```text +Namespace: com/example/user_management Policy: user_access Rules: - ✓ allow_admin: ⨯ False ✓ allow_user: ✓ True Values: - ✓ allow_admin: false ✓ allow_user: true ``` -## Understanding the Output - -The `exec` command shows you: +### Evaluate all exported rules in a policy -1. **Namespace**: The namespace of the policy -2. **Policy**: The policy name -3. **Rules**: Which rules matched (✓) or didn't match (✗) -4. **Values**: The final results of exported rules -5. **Attachments**: The attachments of the exported rules - -## Providing Required Facts - -Since the `user` fact is required (no `?` modifier), you must provide it when executing: +Omit the rule name to run every exported rule: ```bash -sentrie exec user_management/user_access --facts '{"user": {"role": "admin", "status": "active"}}' +sentrie exec com/example/user_management/user_access --facts '{"user": {"role": "admin", "status": "active"}}' ``` -:::note[Note] -If a required fact is not provided, execution will fail with an error. Only optional facts (marked with `?`) can have default values and can be omitted. -::: - -**Expected Output:** +Example stdout: -``` -Namespace: user_management +```text +Namespace: com/example/user_management Policy: user_access Rules: - ✓ allow_user: ✓ True ✓ allow_admin: ✓ True + ✓ allow_user: ✓ True Values: - ✓ allow_user: true ✓ allow_admin: true + ✓ allow_user: true ``` -## Next Steps +### Using a specific pack directory + +```bash +sentrie exec com/example/user_management/user_access --pack /path/to/my-pack --facts '{"user": {"role": "user", "status": "active"}}' +``` + +## Behavior & Constraints + +- **Target format:** `namespace/policy` or `namespace/policy/rule`. Namespace and policy/rule must exist; rule must be exported. +- **Facts JSON:** Keys must match fact names (or aliases) in the policy. Types must satisfy the declared shapes. Required facts missing → evaluation error. +- **Working directory:** If `--pack` is omitted, the current directory is used as the pack root (must contain `*.sentrie` and optionally `sentrie.pack.toml`). +- **Exit code:** 0 on success; non-zero on parse error, missing fact, or evaluation failure. + +## Constraints & Edge Cases -Now that you can run policies, explore the [CLI Reference](/cli-reference/) to learn about all available commands and options. +- Missing required fact → evaluation error; optional fact may be omitted if it has a default. +- Invalid or malformed JSON in `--facts` → CLI error. +- Invalid target (unknown namespace, policy, or rule) → error. +- Rule name in target must be an exported rule; otherwise target is invalid. +- At least one rule must be exported from the policy to be executable via `sentrie exec`. diff --git a/src/content/docs/getting-started/writing-your-first-policy.md b/src/content/docs/getting-started/writing-your-first-policy.md index d76a688..327fe48 100644 --- a/src/content/docs/getting-started/writing-your-first-policy.md +++ b/src/content/docs/getting-started/writing-your-first-policy.md @@ -1,242 +1,113 @@ --- title: Writing your first Policy -description: Learn how to write your first Sentrie policy +description: "Minimal policy structure: namespace, policy, shape, facts, rules, exports." --- -This guide will walk you through creating your first Sentrie policy step by step. +A Sentrie policy file contains one namespace and one or more policies. Each policy declares facts (inputs), rules (decision logic), and exports (rules exposed for evaluation). This page gives the minimal structure and a complete example. -## Basic Policy Structure +## Syntax -A Sentrie policy file consists of exactly one namespace and at least one policy: +```text +namespace SLUG -- **Namespace**: A container for related policies. -- **Policy**: A named collection of rules. +shape ShapeName { field!: type field?: type } -A policy consists of: - -- **Rules**: Individual decision logic. -- **Facts**: Input data for the policy. -- **Exports**: Rules that are exported to make them available for external evaluation. - -## Create a Policy Pack - -```sh -mkdir my-first-policy-pack -cd example-policy-pack -sentrie init example-policy-pack -``` - -## Define a Namespace - -```diff lang=sentrie -// first-policy.sentrie -+ namespace com/example/user_management -``` - -:::note[Remember] -Every file MUST contain a namespace declaration and **MUST** be the first statement in the file. -::: - -## Define a Policy - -```diff lang=sentrie -// first-policy.sentrie -namespace com/example/user_management - -+ policy user_access { -+ -- policy content goes here -+ } -``` - -## Define a Shape - -:::note -Shapes are used to define data structures and aliases. More information about shapes can be found in the [Shapes](/reference/shapes/) reference. -::: - -```diff lang=sentrie -// first-policy.sentrie -namespace com/example/user_management - -+ shape User { -+ role: string -+ status: string -+ } - -policy user_access { - -- policy content goes here -} - -``` - -## Add Facts - -:::note -A fact is a named value that can be injected into policy evaluation at runtime. Every fact MUST have a shape / type annotation. -More information about facts can be found in the [Facts](/reference/facts/) reference. -::: - -```diff lang=sentrie -// first-policy.sentrie -namespace com/example/user_management - -shape User { - role: string - status: string -} - -policy user_access { -+ fact user: User as currentUser -+ fact context?: Context as ctx default {"environment": "production"} -} - -``` - -:::note - -- Facts are **required by default** - they must be provided during execution -- Use `?` to mark facts as **optional** - optional facts can be omitted -- Only **optional facts** (`?`) can have default values -- Facts are **always non-nullable** - null values are not allowed - ::: - -## Add your first rule - -```diff lang=sentrie -// first-policy.sentrie -namespace com/example/user_management - -shape User { - role: string - status: string -} - -policy user_access { - fact user: User as currentUser - fact context?: Context as ctx default {"environment": "production"} - -+ rule allow_admin = { -+ yield user.role == "admin" -+ } +policy IDENT { + fact name: Type [ as alias ] + fact name?: Type [ as alias ] [ default expr ] + rule ruleName = [ default (true|false) ] [ when condition ] { yield expr } + export decision of ruleName } ``` -## Add your second rule - -```diff lang=sentrie -// first-policy.sentrie -namespace com/example/user_management - -shape User { - role: string - status: string -} - -policy user_access { - fact user: User as currentUser +- File must start with exactly one `namespace`. One or more `shape` and `policy` blocks follow. +- Policy must have at least one `rule` and at least one `export decision of`. - rule allow_admin = { - yield user.role == "admin" - } +## Concepts -+ rule allow_user = { -+ yield user.role == "user" and user.status == "active" -+ } -} -``` +| Concept | Required | Description | +| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------ | +| Namespace | Yes | Single `namespace SLUG` per file; first statement. Slash-separated (e.g. `com/example/app`). | +| Shape | No | Data model for facts. Required/optional fields: `field!`, `field?`. | +| Policy | Yes | Named block containing facts, rules, and exports. | +| Fact | No | Input to the policy. Required by default; `?` makes optional. Only optional facts may have `default`. Non-nullable. | +| Rule | Yes (≥1) | Block that yields a decision. May reference other rules in the same policy. | +| Export | Yes (≥1) | `export decision of ruleName` makes the rule callable via CLI/API and importable. | -## Composing Rules +## Examples -Lets use the output of the `allow_admin` rule to update the `allow_user` rule. +### Minimal policy (one rule) -```diff lang=sentrie -// first-policy.sentrie +```sentrie namespace com/example/user_management shape User { - role: string - status: string + role!: string + status!: string } policy user_access { fact user: User as currentUser - rule allow_admin = { - yield user.role == "admin" + rule allow_admin = default false { + yield currentUser.role == "admin" } - rule allow_user = { -- yield user.role == "user" and user.status == "active" -+ yield allow_admin or user.role == "user" and user.status == "active" - } + export decision of allow_admin } ``` -:::note -Here, we are using the output of the `allow_admin` rule to create the `allow_user` rule. This rule grants access if the user is an admin or a user. -::: - -## Export Rules +### Composing rules and exporting multiple rules -```diff lang=sentrie -// first-policy.sentrie +```sentrie namespace com/example/user_management shape User { - role: string - status: string + role!: string + status!: string } policy user_access { fact user: User as currentUser - rule allow_admin = { - yield user.role == "admin" + rule allow_admin = default false { + yield currentUser.role == "admin" } - rule allow_user = { - yield allow_admin or user.role == "user" and user.status == "active" + rule allow_user = default false { + yield allow_admin or (currentUser.role == "user" and currentUser.status == "active") } -+ export decision of allow_admin -+ export decision of allow_user + export decision of allow_admin + export decision of allow_user } ``` -:::note -Rules are exported to make them available for external evaluation. This includes evaluation by the Sentrie CLI or the HTTP API. A policy MUST contain at least one exported rule. -::: - -## Complete Example - -Here's a complete policy that checks user access: +### Optional fact with default ```sentrie -// first-policy.sentrie -namespace com/example/user_management - -shape User { - role: string - status: string -} - policy user_access { - fact user: User as currentUser + fact context?: document as ctx default {"environment": "production"} - rule allow_admin = { - yield user.role == "admin" - } - - rule allow_user = { - yield allow_admin or user.role == "user" and user.status == "active" + rule allow = default false { + yield currentUser.role == "admin" } - - export decision of allow_admin - export decision of allow_user + export decision of allow } ``` -## Next Steps +## Behavior & Constraints + +- **File structure:** One namespace per file; namespace must be the first statement. Shapes and policies follow. +- **Facts:** Declared before rules. Required facts must be provided at evaluation time; optional facts may be omitted or have defaults. +- **Rules:** May reference other rules in the same policy by name (e.g. `yield allow_admin or ...`). At least one rule must be exported for the policy to be executable. +- **Exports:** Only exported rules are available to `sentrie exec` and the HTTP API. A policy must export at least one rule. + +## Constraints & Edge Cases -Now that you've written your first policy, learn how to [run your policy](/getting-started/running-your-policy/) to see it in action. +- Namespace slug uses slashes (e.g. `com/example/app`). No leading/trailing slash. +- Shape field required: `field!: type`. Optional: `field?: type`. +- Fact without `?` is required; missing required fact at evaluation → error. +- Optional fact (`?`) may have `default expr`; if omitted at evaluation, default is used. +- Rule body must yield a value (e.g. `yield expr`). Default when omitted: `default false` or `default true` as declared. From fd39ff12d1b198f162b5a96d5d210bf0578ff3e2 Mon Sep 17 00:00:00 2001 From: Binaek Sarkar Date: Sat, 21 Feb 2026 18:50:28 +0530 Subject: [PATCH 06/21] Enhance CLI documentation with new sections and improved clarity --- astro.config.mjs | 12 +- docs-template.md | 2 +- src/content/docs/cli-reference/exec.md | 246 ++------ src/content/docs/cli-reference/index.md | 573 +----------------- src/content/docs/cli-reference/init.md | 97 +-- src/content/docs/cli-reference/serve.md | 407 +------------ src/content/docs/cli-reference/validate.md | 190 +----- .../deployment-operations/cli-reference.md | 59 -- .../running-as-service.md | 2 +- .../writing-custom-typescript-modules.md | 2 +- .../docs/getting-started/quick-start.md | 2 +- .../getting-started/running-your-policy.md | 12 +- .../pattern-matching-conditionals.md | 2 +- .../language-concepts/policy-composition.md | 2 +- .../docs/reference/arithmetic-operations.md | 2 +- .../docs/reference/boolean-operations.md | 51 +- .../docs/reference/collection-operations.md | 2 +- src/content/docs/reference/constraints.md | 2 +- src/content/docs/reference/facts.md | 2 +- src/content/docs/reference/functions.md | 2 +- src/content/docs/reference/let.md | 2 +- .../docs/reference/membership-operations.md | 63 ++ src/content/docs/reference/namespaces.md | 2 +- src/content/docs/reference/policies.md | 2 +- src/content/docs/reference/precedence.md | 2 +- src/content/docs/reference/rules.md | 2 +- .../reference/security-and-permissions.md | 2 +- src/content/docs/reference/shapes.md | 2 +- src/content/docs/reference/trinary.md | 24 +- .../docs/reference/types-and-values.md | 2 +- .../reference/typescript_modules/index.md | 2 +- .../typescript_modules/sentrie/json.md | 2 +- .../typescript_modules/sentrie/uuid.md | 2 +- 33 files changed, 297 insertions(+), 1481 deletions(-) delete mode 100644 src/content/docs/deployment-operations/cli-reference.md create mode 100644 src/content/docs/reference/membership-operations.md diff --git a/astro.config.mjs b/astro.config.mjs index 54b4443..97571e8 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -57,6 +57,7 @@ export default defineConfig({ { label: "Shapes", slug: "reference/shapes" }, { label: "Arithmetic Operations", slug: "reference/arithmetic-operations" }, { label: "Boolean Operations", slug: "reference/boolean-operations" }, + { label: "Membership Operations", slug: "reference/membership-operations" }, { label: "Collection Operations", slug: "reference/collection-operations" }, { label: "Functions", slug: "reference/functions" }, { label: "Precedence", slug: "reference/precedence" }, @@ -88,10 +89,19 @@ export default defineConfig({ { label: "Writing Custom TypeScript Modules", slug: "extensibility/writing-custom-typescript-modules" }, ], }, + { + label: "CLI Reference", + items: [ + { label: "Overview", slug: "cli-reference" }, + { label: "sentrie exec", slug: "cli-reference/exec" }, + { label: "sentrie init", slug: "cli-reference/init" }, + { label: "sentrie serve", slug: "cli-reference/serve" }, + { label: "sentrie validate", slug: "cli-reference/validate" }, + ], + }, { label: "Deployment & Operations", items: [ - { label: "CLI Reference", slug: "deployment-operations/cli-reference" }, { label: "Running as a Service", slug: "deployment-operations/running-as-service" }, ], }, diff --git a/docs-template.md b/docs-template.md index b763460..8dfb1ae 100644 --- a/docs-template.md +++ b/docs-template.md @@ -8,7 +8,7 @@ // The formal definition or type signature ``` -## Concepts +## Parameters | Name | Type | Required | Description | | :---------- | :----- | :------- | :---------------------------------------------------- | diff --git a/src/content/docs/cli-reference/exec.md b/src/content/docs/cli-reference/exec.md index 812738a..616a63e 100644 --- a/src/content/docs/cli-reference/exec.md +++ b/src/content/docs/cli-reference/exec.md @@ -1,245 +1,79 @@ --- -title: "exec Command" -description: "Execute a policy or rule with Sentrie." +title: "sentrie exec" +description: "Execute a policy or rule with facts; output decisions to stdout." --- - -The `exec` command executes a policy or rule from a policy pack. This is the primary way to test and run your policies locally. +`exec` loads a policy pack, runs the given rule(s) with the supplied facts, and prints decision output. Use it to test policies locally or in scripts. ## Syntax ```bash -sentrie exec [OPTIONS] +sentrie exec [ --pack-location ] [ --facts ] [ --fact-file ] [ --output (table|json) ] ``` -## Description - -The `exec` command loads a policy pack, executes a specific rule or all exported rules in a policy, and displays the results. You can provide facts (input data) via command-line flags or from a JSON file. - -## Arguments - -### `FQN` (required) - -The Fully Qualified Name (FQN) that identifies the namespace, policy, and optionally the rule to execute. - -**Format:** `namespace/policy/rule` or `namespace/policy` - -- **`namespace/policy`** - Execute all exported rules in the policy -- **`namespace/policy/rule`** - Execute only the specific rule - -**Examples:** -- `user_management/user_access` - Execute all exported rules in the `user_access` policy -- `user_management/user_access/allow_user` - Execute only the `allow_user` rule -- `com/example/auth/access_control/check_permission` - Execute a specific rule in a nested namespace +- **TARGET:** `namespace/policy` or `namespace/policy/rule`. Required. +- **--pack-location:** Pack root directory. Default: current directory. +- **--facts:** JSON object of fact names to values. Merged with `--fact-file`; CLI overrides file. +- **--fact-file:** Path to JSON file with a top-level object of facts. +- **--output:** `table` (default) or `json`. ## Options -### `--pack-location` - -Specifies the directory containing the policy pack to load. - -```bash -sentrie exec user_management/user_access --pack-location ./my-policy-pack -``` - -**Default:** `./` (current directory) - -**Examples:** -- `--pack-location ./policies` - Load policies from `./policies` directory -- `--pack-location /path/to/policy-pack` - Load policies from absolute path - -### `--output` - -Specifies the output format for the results. - -```bash -sentrie exec user_management/user_access --output json -``` - -**Default:** `table` - -**Valid values:** -- `table` - Human-readable table format (default) -- `json` - JSON format for programmatic consumption - -**Table Format Example:** -``` -Namespace: user_management -Policy: user_access - -Rules: - ✓ allow_admin: ✓ True - ✓ allow_user: ✓ True - -Values: - ✓ allow_admin: true - ✓ allow_user: true - -Attachments: - ✓ allow_user: - reason: User has admin role -``` - -**JSON Format Example:** -```json -[ - { - "namespace": "user_management", - "policyName": "user_access", - "ruleName": "allow_admin", - "decision": { - "state": "TRUE", - "value": true - }, - "attachments": {} - }, - { - "namespace": "user_management", - "policyName": "user_access", - "ruleName": "allow_user", - "decision": { - "state": "TRUE", - "value": true - }, - "attachments": { - "reason": "User has admin role" - } - } -] -``` - -### `--fact-file` - -Specifies a JSON file containing facts to use for policy execution. - -```bash -sentrie exec user_management/user_access --fact-file ./facts.json -``` - -**Default:** (empty - no file) - -**File Format:** -The file must contain valid JSON with a top-level object: - -```json -{ - "user": { - "role": "admin", - "status": "active" - }, - "context": { - "environment": "production" - } -} -``` - -**Note:** Facts from `--fact-file` are loaded first, then facts from `--facts` flag override any conflicting keys. - -### `--facts` - -Provides facts directly as a JSON string. - -```bash -sentrie exec user_management/user_access --facts '{"user":{"role":"admin","status":"active"}}' -``` +| Name | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| TARGET | string | Yes | `namespace/policy` (all exported rules) or `namespace/policy/rule` (single rule). Slashes separate namespace, policy, and optional rule. | +| `--pack-location` | path | No | Directory containing the policy pack. Default: `./`. | +| `--facts` | JSON string | No | Inline facts. Keys match policy fact names or aliases. Overrides same keys from `--fact-file`. | +| `--fact-file` | path | No | Path to JSON file; top-level object gives facts. Loaded first; `--facts` overrides. | +| `--output` | enum | No | `table` or `json`. Default: `table`. | -**Default:** `{}` (empty object) - -**Fact Merging:** -If both `--fact-file` and `--facts` are provided, the facts from `--facts` will override any conflicting keys from the file. This allows you to use a base fact file and override specific values on the command line. - -**Example:** -```bash -# facts.json contains: {"user": {"role": "user", "status": "active"}} -# Command line overrides the role -sentrie exec user_management/user_access \ - --fact-file ./facts.json \ - --facts '{"user":{"role":"admin"}}' -# Result: user.role = "admin", user.status = "active" -``` +**Returns:** Exit 0 on success; non-zero on error. Output to stdout: table (human-readable) or JSON array of decision objects (namespace, policyName, ruleName, decision.state, decision.value, attachments). ## Examples -### Execute a specific rule with inline facts +### Basic Usage -```bash -sentrie exec user_management/user_access/allow_user \ - --facts '{"user":{"role":"admin","status":"active"}}' -``` - -### Execute all exported rules in a policy +Execute a single rule with inline facts: ```bash -sentrie exec user_management/user_access \ - --facts '{"user":{"role":"admin","status":"active"}}' +sentrie exec com/example/user_management/user_access/allow_user --facts '{"user":{"role":"user","status":"active"}}' ``` -### Execute with facts from a file +Execute all exported rules in a policy: ```bash -sentrie exec user_management/user_access \ - --fact-file ./user-facts.json +sentrie exec com/example/user_management/user_access --facts '{"user":{"role":"admin","status":"active"}}' ``` -### Execute with facts from file and override specific values +### Advanced Usage -```bash -sentrie exec user_management/user_access \ - --fact-file ./base-facts.json \ - --facts '{"user":{"role":"admin"}}' -``` - -### Execute and output as JSON +Facts from file with command-line overrides; JSON output: ```bash -sentrie exec user_management/user_access \ +sentrie exec com/example/user_management/user_access \ + --fact-file ./base-facts.json \ --facts '{"user":{"role":"admin"}}' \ --output json ``` -### Execute from a different pack location - -```bash -sentrie exec com/example/auth/access_control/check_permission \ - --pack-location ./policy-pack \ - --fact-file ./user-facts.json \ - --output json -``` - -### Pipe JSON output to another tool +Custom pack path and JSON output for piping: ```bash -sentrie exec user_management/user_access \ - --facts '{"user":{"role":"admin"}}' \ - --output json | jq '.[0].decision.value' +sentrie exec com/example/auth/access/allow --pack-location ./policy-pack --facts '{"user":{"role":"admin"}}' --output json ``` -## Decision States - -The command output includes decision states: - -- **`TRUE`** (✓ True) - The rule evaluated to true -- **`FALSE`** (⨯ False) - The rule evaluated to false -- **`UNKNOWN`** (• Unknown) - The rule evaluated to unknown (e.g., when `when` condition is false and no default is provided) - -## Error Handling - -If the command encounters errors: - -- **Invalid FQN**: Returns an error if the namespace, policy, or rule is not found -- **Invalid facts**: Returns an error if facts don't match expected types or shapes -- **Policy errors**: Returns an error if policy evaluation fails -- **File errors**: Returns an error if `--fact-file` cannot be read or parsed - -## Output Destination +## Behavior & Constraints -All output is written to **stdout**, making it easy to: -- Pipe results to other commands -- Redirect to files -- Process programmatically (with JSON output) +- **Facts:** Required facts must be present (in `--facts` and/or `--fact-file`). Optional facts may be omitted if they have defaults. Keys must match fact names or aliases; types must satisfy declared shapes. +- **Merge order:** Facts from `--fact-file` are loaded first; `--facts` overrides conflicting keys. +- **Output:** Table format lists namespace, policy, rules (match and value), values, and attachments. JSON format is an array of objects with namespace, policyName, ruleName, decision (state, value), attachments. +- **Decision states:** `TRUE`, `FALSE`, or `UNKNOWN` (e.g. when `when` is false and no default). -## See Also +## Constraints & Edge Cases -- [Executing Policies](/running-sentrie/executing-policies) - Detailed guide on executing policies -- [CLI Reference](/cli-reference) - Complete CLI documentation -- [Policy Language Reference](/reference) - Learn about writing policies +- Invalid or missing TARGET (unknown namespace/policy/rule) → error; rule must be exported. +- Missing required fact → evaluation error. +- Invalid JSON in `--facts` or in `--fact-file` → error. +- `--fact-file` path must exist and be readable. +- Pack directory must contain loadable `*.sentrie` (and optional `sentrie.pack.toml`). Policy/parse errors → error. +- For full HTTP API behavior, see [Running as a Service](/deployment-operations/running-as-service). diff --git a/src/content/docs/cli-reference/index.md b/src/content/docs/cli-reference/index.md index 37b22c6..fcf8b1b 100644 --- a/src/content/docs/cli-reference/index.md +++ b/src/content/docs/cli-reference/index.md @@ -1,564 +1,39 @@ --- -title: "CLI Reference" -description: "Complete reference for the Sentrie command-line interface." +title: "Sentrie CLI" +description: "Command-line reference: sub-commands, global options, and exit codes." --- -This document provides comprehensive reference for the Sentrie command-line interface. +The Sentrie CLI runs policy evaluations, initializes packs, serves the HTTP API, and validates policy packs. All commands support the global options below. -## Overview +## Sub-Commands -Sentrie provides a command-line interface for running policy servers and managing policy packs. The CLI is built on top of the `cling` framework and provides a consistent, user-friendly experience. +| Command | Description | +| :--- | :--- | +| [sentrie exec](/cli-reference/exec) | Execute a policy or rule with facts. | +| [sentrie init](/cli-reference/init) | Create a new policy pack in the current directory. | +| [sentrie serve](/cli-reference/serve) | Start the HTTP server for policy evaluation. | +| [sentrie validate](/cli-reference/validate) | Validate pack structure, syntax, and types. | ## Global Options -All Sentrie commands support these global options: +All commands support these options: -| Option | Description | Default | -| ----------------- | ---------------------------------------- | ------- | -| `--help`, `-h` | Show help information | - | -| `--version`, `-v` | Show version information | - | -| `--debug` | Enable debug logging | `false` | -| `--log-level` | Set log level (DEBUG, INFO, WARN, ERROR) | `INFO` | +| Option | Description | Default | +| :--- | :--- | :--- | +| `--help`, `-h` | Show help for the command. | — | +| `--version`, `-v` | Show Sentrie version. | — | +| `--debug` | Enable debug logging. | `false` | +| `--log-level` | Log level: DEBUG, INFO, WARN, ERROR. | `INFO` | -## Commands +## Exit Codes -### `exec` +| Value | Description | +| :--- | :--- | +| `0` | Success. | +| Non-zero | Failure (e.g. parse error, missing fact, invalid target, port in use). | -Execute a policy or rule from a policy pack. - -See the [exec command documentation](./exec) for details. - -### `init` - -Initialize a new policy pack. - -See the [init command documentation](./init) for details. - -### `serve` - -Start the Sentrie HTTP server to evaluate policies. - -#### Syntax - -```bash -sentrie serve [OPTIONS] -``` - -#### Options - -| Option | Type | Default | Description | -| ----------------- | -------- | ----------- | --------------------------------- | -| `--port` | int | `7529` | Port to listen on | -| `--pack-location` | string | `./` | Directory containing policy files | -| `--listen` | []string | `["local"]` | Address(es) to listen on | - -#### Examples - -```bash -# Start server on default port -sentrie serve - -# Start server on custom port -sentrie serve --port 8080 - -# Start server with custom pack location -sentrie serve --pack-location /path/to/policies - -# Start server on specific addresses -sentrie serve --listen 0.0.0.0 --listen 127.0.0.1 - -# Start server with debug logging -sentrie serve --debug --log-level DEBUG -``` - -#### Environment Variables - -The `serve` command respects these environment variables: - -| Variable | Description | Default | -| ------------------- | -------------------- | ------- | -| `SENTRIE_DEBUG` | Enable debug logging | `false` | -| `SENTRIE_LOG_LEVEL` | Log level | `INFO` | -| `SENTRIE_PORT` | Default port | `7529` | - -#### Server Behavior - -When you start the server, it will: - -1. Load the policy pack from the specified location -2. Parse and validate all `.sentrie` files -3. Create an index of policies and rules -4. Start the HTTP server on the specified port -5. Log startup information and any errors - -#### Pack Loading - -The server looks for policy files in the specified directory: - -- **Policy files**: `*.sentrie` - Sentrie policy files -- **Pack file**: `sentrie.pack.toml` - Pack metadata (optional) -- **JavaScript modules**: `*.js` - JavaScript modules for `use` statements - -#### Error Handling - -If the server encounters errors during startup: - -- **Policy parsing errors**: Server will not start, errors are logged -- **Pack loading errors**: Server will not start, errors are logged -- **Port binding errors**: Server will not start, error is logged -- **Runtime errors**: Server continues running, errors are logged - -#### Graceful Shutdown - -The server supports graceful shutdown: - -- **SIGINT** (Ctrl+C): Graceful shutdown -- **SIGTERM**: Graceful shutdown -- **SIGKILL**: Immediate shutdown - -### `validate` - -Validate a policy pack's structure, syntax, and type correctness. - -#### Syntax - -```bash -sentrie validate [OPTIONS] -``` - -#### Options - -| Option | Type | Default | Description | -| ----------------- | ------ | ------- | --------------------------------- | -| `--pack-location` | string | `./` | Directory containing policy files | -| `--facts` | string | `{}` | Facts for type checking | - -#### Examples - -```bash -# Validate a policy pack -sentrie validate user_management/user_access - -# Validate with facts for type checking -sentrie validate user_management/user_access --facts '{"user":{"role":"admin"}}' - -# Validate from a specific location -sentrie validate com/example/auth/access_control --pack-location ./policies -``` - -See the [validate command documentation](./validate) for details. +For `exec` and `validate`, exit 0 when evaluation or validation succeeds; non-zero on error. For `serve`, the process runs until SIGINT/SIGTERM; non-zero on startup failure (e.g. port in use, pack load error). ## HTTP API -When the server is running, it provides a REST API for policy evaluation. - -### Base URL - -``` -http://localhost:7529 -``` - -### Endpoints - -#### Decision Execution - -**POST** `/decision/{target...}` - -Execute a policy or rule. The `{target...}` path parameter contains the full path to the namespace, policy, and optionally the rule. - -##### Path Format - -- `/decision/{namespace}/{policy}/{rule}` - Execute a specific rule -- `/decision/{namespace}/{policy}` - Execute all exported rules in a policy - -The path is resolved to extract: -- `namespace`: The namespace (all segments except the last two) -- `policy`: The policy name (second to last segment) -- `rule`: The rule name (last segment, optional) - -##### Request Body - -The request body must be a JSON object with a `facts` field: - -```json -{ - "facts": { - "user": { - "id": "user123", - "role": "admin" - }, - "resource": { - "id": "resource456", - "owner": "user123" - } - } -} -``` - -##### Response - -The response is a JSON object containing an array of decisions: - -```json -{ - "decisions": [ - { - "policy": "user", - "namespace": "com/example/auth", - "rule": "isAdmin", - "decision": { - "state": "TRUE", - "value": true - }, - "attachments": { - "role": "admin" - }, - "trace": { ... } - } - ], - "error": "" -} -``` - -##### Query Parameters - -Query parameters are parsed as run configuration (currently parsed but not used in execution). - -##### Error Responses - -Errors are returned using RFC 9457 Problem Details format with `Content-Type: application/problem+json`: - -**400 Bad Request** -```json -{ - "type": "https://sentrie.sh/problems/400", - "title": "Invalid JSON", - "status": 400, - "detail": "The request body could not be parsed as valid JSON", - "instance": "request-id-12345" -} -``` - -**404 Not Found** -```json -{ - "type": "https://sentrie.sh/problems/404", - "title": "Invalid Path", - "status": 404, - "detail": "Policy 'com/example/auth/user' not found", - "instance": "request-id-12345" -} -``` - -**405 Method Not Allowed** -```json -{ - "type": "https://sentrie.sh/problems/405", - "title": "Method Not Allowed", - "status": 405, - "detail": "Only POST requests are supported for this endpoint", - "instance": "request-id-12345" -} -``` - -### Example Requests - -#### Simple Authorization - -```bash -curl -X POST "http://localhost:7529/decision/com/example/auth/user/allow" \ - -H "Content-Type: application/json" \ - -d '{ - "facts": { - "user": {"id": "user123", "role": "admin"} - } - }' -``` - -#### Resource Access Control - -```bash -curl -X POST "http://localhost:7529/decision/com/example/resources/document/canRead" \ - -H "Content-Type: application/json" \ - -d '{ - "facts": { - "user": {"id": "user123", "role": "user"}, - "document": {"id": "doc456", "owner": "user123"} - } - }' -``` - -#### Complex Policy with Attachments - -```bash -curl -X POST "http://localhost:7529/decision/com/example/billing/pricing/calculatePrice" \ - -H "Content-Type: application/json" \ - -d '{ - "facts": { - "user": {"id": "user123", "isPremium": true}, - "product": {"id": "prod789", "price": 100.0} - } - }' -``` - -## Configuration - -### Pack Configuration - -Create a `sentrie.pack.toml` file in your pack directory: - -```toml -schema_version = "0.1.0" -name = "my-policy-pack" -version = "1.0.0" -description = "My policy pack" -license = "MIT" -repository = "https://github.com/myorg/my-policy-pack" - -[engines] -sentrie = "0.1.0" - -[authors] -"John Doe" = "john@example.com" - -[permissions] -fs_read = ["/etc/passwd"] -net = ["http://example.com"] - -[metadata] -"custom" = "value" -``` - -### Environment Configuration - -Set environment variables for configuration: - -```bash -export SENTRIE_DEBUG=true -export SENTRIE_LOG_LEVEL=DEBUG -export SENTRIE_PORT=8080 -sentrie serve -``` - -### Logging Configuration - -Configure logging levels and output: - -```bash -# Debug logging -sentrie serve --debug - -# Custom log level -sentrie serve --log-level WARN - -# Environment variable -export SENTRIE_LOG_LEVEL=ERROR -sentrie serve -``` - -## Troubleshooting - -### Common Issues - -#### Port Already in Use - -```bash -# Error: port 7529 is already in use -# Solution: Use a different port -sentrie serve --port 8080 -``` - -#### Policy Not Found - -```bash -# Error: Policy 'com/example/auth/user' not found -# Solution: Check namespace and policy names -# Make sure the policy file exists and is valid -``` - -#### Invalid Policy Syntax - -```bash -# Error: Policy parsing failed -# Solution: Check the policy file syntax -# Use --debug for detailed error messages -``` - -#### Pack Loading Failed - -```bash -# Error: Pack loading failed -# Solution: Check the pack directory exists -# Verify sentrie.pack.toml is valid -``` - -### Debug Mode - -Enable debug mode for detailed logging: - -```bash -sentrie serve --debug --log-level DEBUG -``` - -This will show: - -- Policy loading progress -- Detailed error messages -- Request/response logging -- Performance metrics - -### Performance Tuning - -#### Memory Usage - -The server uses memory for: - -- Policy index -- JavaScript VM pools -- Call memoization cache -- Module bindings - -Monitor memory usage and adjust cache sizes if needed. - -#### Concurrent Requests - -The server handles concurrent requests efficiently: - -- Each request gets its own execution context -- JavaScript VMs are pooled for reuse -- Policy evaluation is stateless - -## Examples - -### Complete Example - -1. **Create a policy pack**: - -```bash -mkdir my-policy-pack -cd my-policy-pack -``` - -2. **Create a policy file**: - -```text -# auth.sentrie -namespace com/example/auth - -policy user { - rule allow = default false when user.role == "admin" { - yield true - } - - export decision of allow -} -``` - -3. **Create a pack file**: - -```toml -# sentrie.pack.toml -schema_version = "0.1.0" -name = "my-policy-pack" -version = "1.0.0" -description = "My policy pack" - -[engines] -sentrie = "0.1.0" -``` - -4. **Start the server**: - -```bash -sentrie serve --pack-location . --port 8080 -``` - -5. **Test the policy**: - -```bash -curl -X POST "http://localhost:8080/decision/com/example/auth/user/allow" \ - -H "Content-Type: application/json" \ - -d '{ - "facts": { - "user": {"role": "admin"} - } - }' -``` - -### Production Deployment - -For production deployment: - -1. **Use a reverse proxy** (nginx, Apache) -2. **Enable HTTPS** with SSL certificates -3. **Set up monitoring** and logging -4. **Configure load balancing** for high availability -5. **Use environment variables** for configuration -6. **Set up health checks** for the API - -### Docker Deployment - -```dockerfile -FROM golang:1.25-alpine AS builder -WORKDIR /app -COPY . . -RUN go build -o sentrie . - -FROM alpine:latest -RUN apk --no-cache add ca-certificates -WORKDIR /root/ -COPY --from=builder /app/sentrie . -COPY --from=builder /app/policies ./policies -EXPOSE 7529 -CMD ["./sentrie", "serve", "--pack-location", "./policies"] -``` - -## Best Practices - -### 1. Use Descriptive Names - -```bash -# Good -sentrie serve --pack-location ./production-policies - -# Bad -sentrie serve --pack-location ./p -``` - -### 2. Organize Policies - -``` -policies/ -├── auth/ -│ ├── user.sentrie -│ └── admin.sentrie -├── billing/ -│ └── pricing.sentrie -└── sentrie.pack.toml -``` - -### 3. Use Environment Variables - -```bash -# production.sh -export SENTRIE_LOG_LEVEL=WARN -export SENTRIE_PORT=8080 -sentrie serve --pack-location ./policies -``` - -### 4. Monitor Performance - -```bash -# Enable debug logging to monitor performance -sentrie serve --debug --log-level DEBUG -``` - -### 5. Handle Errors Gracefully - -```bash -# Check exit codes -if ! sentrie serve; then - echo "Failed to start server" - exit 1 -fi -``` +When the server is running (`sentrie serve`), it exposes a REST API for policy evaluation. For request/response format, error codes, and deployment details, see [Running as a Service](/deployment-operations/running-as-service). diff --git a/src/content/docs/cli-reference/init.md b/src/content/docs/cli-reference/init.md index b8871e4..15822c2 100644 --- a/src/content/docs/cli-reference/init.md +++ b/src/content/docs/cli-reference/init.md @@ -1,105 +1,60 @@ --- -title: "init" -description: "Initialize a new policy pack." +title: "sentrie init" +description: "Create a new policy pack with sentrie.pack.toml in the current or given directory." --- -The `init` command initializes a new policy pack in the provided directory with the provided name. It creates a `sentrie.pack.toml` file with the correct structure and validates the pack name. +`init` creates a policy pack by writing a `sentrie.pack.toml` with the given pack name. Use it to bootstrap a new pack or ensure correct pack structure. ## Syntax ```bash -sentrie init {NAME} [OPTIONS] +sentrie init [ --directory ] ``` -## Arguments - -### `NAME` (required) - -The name of the policy pack. The name must be a valid identifier: - -- Must start with a letter (a-z, A-Z) -- Can contain letters, numbers, underscores (`_`), hyphens (`-`), and dots (`.`) -- Dots can be used for hierarchical names (e.g., `com.example.pack`) -- Each segment after a dot must also start with a letter - -**Valid examples:** - -- `my-policy-pack` -- `my_policy_pack` -- `myPolicyPack` -- `com.example.pack` -- `org.mycompany.policies` - -**Invalid examples:** - -- `123pack` (starts with a number) -- `-mypack` (starts with a hyphen) -- `my..pack` (double dot) -- `my.123pack` (segment after dot starts with a number) +- **NAME:** Pack name (required). Must be a valid identifier: start with a letter; letters, numbers, underscores, hyphens, dots allowed; segments after a dot must start with a letter. +- **--directory:** Directory to create the pack in. Must be empty. Default: current directory. ## Options -### `--directory` - -Specifies the directory to initialize the policy pack in. - -```bash -sentrie init my-policy-pack --directory ./my-policy-pack -``` - -:::warning[Important] -The directory **MUST be empty**. If the directory contains any files, the command will fail with an error. -::: +| Name | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| NAME | string | Yes | Pack name. Valid: e.g. `my-policy-pack`, `com.example.pack`. Invalid: leading digit, leading hyphen, double dot, segment after dot starting with digit. | +| `--directory` | path | No | Directory for the pack. Must be empty. Default: `./`. | -**Default**: `./` (current directory) - -## What Gets Created - -The command creates a `sentrie.pack.toml` file with the following structure: - -```toml -[schema] -version = 1 - -[pack] -name = "my-policy-pack" -version = "0.0.1" -``` - -The pack file is validated against the Sentrie pack schema to ensure it's correctly formatted. +**Returns:** Exit 0 on success. Creates `sentrie.pack.toml` with `[schema] version = 1`, `[pack] name = ""`, `version = "0.0.1"`. No other files are created. ## Examples -Create a policy pack in the current directory: +### Basic Usage + +Create a pack in the current directory (must be empty): ```bash sentrie init my-policy-pack ``` -Create a policy pack in a new directory: +Create a pack in a new directory: ```bash sentrie init my-policy-pack --directory ./my-policy-pack ``` -Create a policy pack with a hierarchical name: +### Advanced Usage + +Hierarchical pack name: ```bash sentrie init com.example.iam --directory ./iam-pack ``` -## Error Messages - -If the pack name is invalid, you'll see an error: +## Behavior & Constraints -```bash -$ sentrie init 123pack -Error: name needs to be a valid identity. It must start with a letter and can only contain letters, numbers, underscores and `dot`. -``` +- **Directory:** Must exist and be empty. If the directory contains any files, the command fails. +- **Pack name:** Validated against identifier rules; invalid name produces an error and no file is written. +- **File created:** Only `sentrie.pack.toml` is written; structure matches the Sentrie pack schema. -If the directory is not empty: +## Constraints & Edge Cases -```bash -$ sentrie init my-pack -Error: directory is not empty - please choose a different directory -``` +- Invalid name (e.g. `123pack`, `-mypack`, `my..pack`, `my.123pack`) → error; no pack file created. +- Non-empty directory → error (e.g. "directory is not empty - please choose a different directory"). +- `--directory` must point to an existing directory; creation of the directory is not performed by `init`. diff --git a/src/content/docs/cli-reference/serve.md b/src/content/docs/cli-reference/serve.md index d5bb061..1a65e09 100644 --- a/src/content/docs/cli-reference/serve.md +++ b/src/content/docs/cli-reference/serve.md @@ -1,413 +1,70 @@ --- -title: "serve Command" -description: "Start the Sentrie HTTP server to evaluate policies." +title: "sentrie serve" +description: "Start the HTTP server for policy evaluation; load pack from a directory." --- - -The `serve` command starts the Sentrie HTTP server to evaluate policies. +`serve` starts an HTTP server that loads a policy pack and exposes a REST API for evaluating policies. Use it for local development or as the process behind a reverse proxy in production. ## Syntax ```bash -sentrie serve [OPTIONS] +sentrie serve [ --pack-location ] [ --port ] [ --listen ... ] ``` -## Description - -The `serve` command starts an HTTP server that provides a REST API for evaluating Sentrie policies. The server loads policy files from a specified directory, creates an index of available policies and rules, and starts listening for HTTP requests. +- **--pack-location:** Directory containing the policy pack. Default: `./`. +- **--port:** Port to listen on. Default: `7529`. +- **--listen:** Address(es) to bind (e.g. `local`, `0.0.0.0`, `127.0.0.1`). Default: `["local"]`. May be repeated. ## Options -| Option | Type | Default | Description | -| ----------------- | -------- | ----------- | --------------------------------- | -| `--port` | int | `7529` | Port to listen on | -| `--pack-location` | string | `./` | Directory containing policy files | -| `--listen` | []string | `["local"]` | Address(es) to listen on | - -### --port - -Specifies the port number for the HTTP server to listen on. - -```bash -sentrie serve --port 8080 -``` - -**Default**: `7529` (PLCY on a phone keypad) - -**Examples**: - -- `--port 8080` - Listen on port 8080 -- `--port 3000` - Listen on port 3000 - -### --pack-location - -Specifies the directory containing Sentrie policy files. - -```bash -sentrie serve --pack-location /path/to/policies -``` - -**Default**: `./` (current directory) - -**Examples**: - -- `--pack-location ./policies` - Load policies from `./policies` directory -- `--pack-location /etc/sentrie/policies` - Load policies from `/etc/sentrie/policies` - -**Requirements**: +| Name | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| `--pack-location` | path | No | Pack root (e.g. `*.sentrie`, optional `sentrie.pack.toml`, `*.js`). Default: `./`. | +| `--port` | int | No | Listen port. Default: `7529`. | +| `--listen` | string (repeatable) | No | Bind address: `local` (localhost), `0.0.0.0` (all interfaces), or specific IP. Default: `["local"]`. | -- Directory must exist -- Directory must contain `.sentrie` policy files -- Optional `sentrie.pack.toml` file for pack metadata - -### --listen - -Specifies the network addresses to listen on. - -```bash -sentrie serve --listen 0.0.0.0 --listen 127.0.0.1 -``` - -**Default**: `["local"]` (localhost only) - -**Examples**: - -- `--listen local` - Listen on localhost only -- `--listen 0.0.0.0` - Listen on all interfaces -- `--listen 127.0.0.1` - Listen on localhost -- `--listen 192.168.1.100` - Listen on specific IP - -**Security Note**: Listening on `0.0.0.0` makes the server accessible from any network interface. Use with caution in production environments. - -## Environment Variables - -The `serve` command respects these environment variables: - -| Variable | Description | Default | -| ------------------- | ------------------------------------ | ------- | -| `SENTRIE_DEBUG` | Enable debug logging | `false` | -| `SENTRIE_LOG_LEVEL` | Log level (DEBUG, INFO, WARN, ERROR) | `INFO` | -| `SENTRIE_PORT` | Default port | `7529` | +**Returns:** Process runs until SIGINT/SIGTERM (graceful shutdown). Exit non-zero on startup failure (e.g. port in use, pack load or parse error). HTTP API base: `http://:`; see [Running as a Service](/deployment-operations/running-as-service) for request/response format. ## Examples ### Basic Usage -```bash -# Start server with defaults -sentrie serve - -# Start server on custom port -sentrie serve --port 8080 - -# Start server with custom pack location -sentrie serve --pack-location ./my-policies -``` - -### Production Configuration +Start with defaults (current directory, port 7529, localhost only): ```bash -# Production setup with environment variables -export SENTRIE_LOG_LEVEL=WARN -export SENTRIE_PORT=8080 -sentrie serve --pack-location /etc/sentrie/policies --listen 0.0.0.0 -``` - -### Development Setup - -```bash -# Development setup with debug logging -sentrie serve --debug --log-level DEBUG --pack-location ./policies -``` - -### Multiple Listen Addresses - -```bash -# Listen on multiple addresses -sentrie serve --listen 127.0.0.1 --listen 192.168.1.100 --port 8080 -``` - -## Server Behavior - -### Startup Process - -1. **Load Pack**: Load policy pack from specified directory -2. **Parse Policies**: Parse all `.sentrie` files -3. **Validate Policies**: Check syntax and semantics -4. **Create Index**: Build index of policies and rules -5. **Start Server**: Begin listening for HTTP requests -6. **Log Status**: Log startup information and any errors - -### Pack Loading - -The server looks for the following files in the pack directory: - -- **Policy Files**: `*.sentrie` - Sentrie policy files -- **Pack File**: `sentrie.pack.toml` - Pack metadata (optional) -- **JavaScript Modules**: `*.js` - JavaScript modules for `use` statements - -### Error Handling - -If the server encounters errors during startup: - -- **Policy Parsing Errors**: Server will not start, errors are logged -- **Pack Loading Errors**: Server will not start, errors are logged -- **Port Binding Errors**: Server will not start, error is logged -- **Runtime Errors**: Server continues running, errors are logged - -### Graceful Shutdown - -The server supports graceful shutdown on these signals: - -- **SIGINT** (Ctrl+C): Graceful shutdown -- **SIGTERM**: Graceful shutdown -- **SIGKILL**: Immediate shutdown - -## HTTP API - -Once started, the server provides a REST API at: - -``` -http://localhost:7529 -``` - -### Decision Endpoint - -**POST** `/decision/{namespace}/{policy}/{rule}` - -Execute a specific rule with provided facts. - -**Example**: - -```bash -curl -X POST "http://localhost:7529/decision/com/example/auth/user/allow" \ - -H "Content-Type: application/json" \ - -d '{"user": {"role": "admin"}}' -``` - -## Troubleshooting - -### Common Issues - -#### Port Already in Use - -```bash -# Error: port 7529 is already in use -# Solution: Use a different port -sentrie serve --port 8080 -``` - -#### Policy Not Found - -```bash -# Error: Policy 'com/example/auth/user' not found -# Solution: Check namespace and policy names -# Make sure the policy file exists and is valid -``` - -#### Invalid Policy Syntax - -```bash -# Error: Policy parsing failed -# Solution: Check the policy file syntax -# Use --debug for detailed error messages -``` - -#### Pack Loading Failed - -```bash -# Error: Pack loading failed -# Solution: Check the pack directory exists -# Verify sentrie.pack.toml is valid -``` - -### Debug Mode - -Enable debug mode for detailed logging: - -```bash -sentrie serve --debug --log-level DEBUG -``` - -This will show: - -- Policy loading progress -- Detailed error messages -- Request/response logging -- Performance metrics - -### Log Levels - -| Level | Description | -| ------- | ------------------------------ | -| `DEBUG` | Detailed debugging information | -| `INFO` | General information messages | -| `WARN` | Warning messages | -| `ERROR` | Error messages only | - -## Performance Considerations - -### Memory Usage - -The server uses memory for: - -- Policy index -- JavaScript VM pools -- Call memoization cache -- Module bindings - -### Concurrent Requests - -The server handles concurrent requests efficiently: - -- Each request gets its own execution context -- JavaScript VMs are pooled for reuse -- Policy evaluation is stateless - -### Caching - -The server includes several caching mechanisms: - -- **Call Memoization**: Caches function call results -- **Module Bindings**: Caches JavaScript module bindings -- **Policy Index**: Caches parsed policy information - -## Security Considerations - -### Network Security - -- **Localhost Only**: Default configuration only listens on localhost -- **Firewall**: Use firewall rules to restrict access -- **HTTPS**: Use a reverse proxy for HTTPS termination -- **Authentication**: Implement authentication at the application level - -### File System Security - -- **Read-Only**: Policy files should be read-only -- **Permissions**: Restrict access to policy directories -- **Validation**: Validate all input data - -## Best Practices - -### 1. Use Environment Variables - -```bash -# production.sh -export SENTRIE_LOG_LEVEL=WARN -export SENTRIE_PORT=8080 -sentrie serve --pack-location ./policies -``` - -### 2. Organize Policies - -``` -policies/ -├── auth/ -│ ├── user.sentrie -│ └── admin.sentrie -├── billing/ -│ └── pricing.sentrie -└── sentrie.pack.toml -``` - -### 3. Monitor Performance - -```bash -# Enable debug logging to monitor performance -sentrie serve --debug --log-level DEBUG -``` - -### 4. Handle Errors Gracefully - -```bash -# Check exit codes -if ! sentrie serve; then - echo "Failed to start server" - exit 1 -fi +sentrie serve ``` -### 5. Use Process Managers - -For production deployments, use process managers like: - -- **systemd** (Linux) -- **supervisor** (Cross-platform) -- **PM2** (Node.js ecosystem) -- **Docker** (Containerized deployments) - -## Examples - -### Complete Example - -1. **Create a policy pack**: +Custom port and pack path: ```bash -mkdir my-policy-pack -cd my-policy-pack -``` - -2. **Create a policy file**: - -```text -# auth.sentrie -namespace com/example/auth - -policy user { - rule allow = default false when user.role == "admin" { - yield true - } - - export decision of allow -} +sentrie serve --port 8080 --pack-location ./my-policies ``` -3. **Create a pack file**: - -```toml -# sentrie.pack.toml -schema_version = "0.1.0" -name = "my-policy-pack" -version = "1.0.0" -description = "My policy pack" - -[engines] -sentrie = "0.1.0" -``` +### Advanced Usage -4. **Start the server**: +Listen on all interfaces and set pack path (e.g. production-style): ```bash -sentrie serve --pack-location . --port 8080 +sentrie serve --pack-location /etc/sentrie/policies --listen 0.0.0.0 --port 8080 ``` -5. **Test the policy**: +Multiple listen addresses and debug logging: ```bash -curl -X POST "http://localhost:8080/decision/com/example/auth/user/allow" \ - -H "Content-Type: application/json" \ - -d '{"user": {"role": "admin"}}' +sentrie serve --listen 127.0.0.1 --listen 192.168.1.100 --port 8080 --debug --log-level DEBUG ``` -### Production Deployment +## Behavior & Constraints -```bash -# Production setup -export SENTRIE_LOG_LEVEL=WARN -export SENTRIE_PORT=8080 -sentrie serve \ - --pack-location /etc/sentrie/policies \ - --listen 0.0.0.0 \ - --port 8080 -``` +- **Startup:** Loads pack from `--pack-location`, parses `*.sentrie`, validates, builds index, then listens. Policy or pack errors prevent startup. +- **Shutdown:** SIGINT (Ctrl+C) and SIGTERM trigger graceful shutdown; SIGKILL does not. +- **Environment:** `SENTRIE_DEBUG`, `SENTRIE_LOG_LEVEL`, `SENTRIE_PORT` can override debug, log level, and port when not set via flags. +- **Output:** All output to stdout/stderr; no file logging by default. -### Development Setup +## Constraints & Edge Cases -```bash -# Development setup -sentrie serve \ - --debug \ - --log-level DEBUG \ - --pack-location ./policies \ - --port 3000 -``` +- Port already in use → startup fails; choose another `--port`. +- Invalid or missing pack directory, or pack load/parse failure → startup fails; fix pack or path. +- Listening on `0.0.0.0` exposes the server on all interfaces; secure with firewall, reverse proxy, and/or auth. HTTPS and auth are not provided by `serve`; use a reverse proxy. +- Full HTTP API schema, error codes, and deployment details: [Running as a Service](/deployment-operations/running-as-service). diff --git a/src/content/docs/cli-reference/validate.md b/src/content/docs/cli-reference/validate.md index 041017a..9e4b124 100644 --- a/src/content/docs/cli-reference/validate.md +++ b/src/content/docs/cli-reference/validate.md @@ -1,194 +1,64 @@ --- -title: "validate Command" -description: "Validate a policy pack and its structure." +title: "sentrie validate" +description: "Validate pack structure, syntax, and types without executing policies." --- - -The `validate` command validates a policy pack's structure, syntax, and type correctness without executing policies. This is useful for checking that your policies are correctly formatted and can be loaded before deployment. +`validate` loads a policy pack and checks pack file, policy syntax, types, and references. It does not run policies. Use it in CI or before deployment to catch errors early. ## Syntax ```bash -sentrie validate [OPTIONS] +sentrie validate [ --pack-location ] [ --facts ] ``` -## Description - -The `validate` command performs comprehensive validation of a policy pack: - -1. **Pack Loading**: Validates the pack file (`sentrie.pack.toml`) structure -2. **Program Loading**: Loads and parses all `.sentrie` policy files -3. **Index Validation**: Validates namespace, policy, and rule references -4. **Type Checking**: Validates type annotations and constraints -5. **Executor Creation**: Attempts to create an executor to verify the pack is executable - -If validation succeeds, the command exits with code 0. If any validation fails, it exits with a non-zero code and displays error messages. - -## Arguments - -### `FQN` (required) - -The Fully Qualified Name (FQN) that identifies the namespace and policy to validate. The rule component is optional but can be included for reference. - -**Format:** `namespace/policy` or `namespace/policy/rule` - -**Examples:** -- `user_management/user_access` - Validate the `user_access` policy -- `com/example/auth/access_control` - Validate a policy in a nested namespace -- `com/example/auth/access_control/check_permission` - Validate with rule reference (rule is not validated, only used for context) +- **TARGET:** `namespace/policy` or `namespace/policy/rule`. Required. Identifies the policy (and optionally rule) to validate in context. +- **--pack-location:** Pack root directory. Default: `./`. +- **--facts:** Optional JSON object for type-checking facts against declarations. ## Options -### `--pack-location` - -Specifies the directory containing the policy pack to validate. - -```bash -sentrie validate user_management/user_access --pack-location ./my-policy-pack -``` - -**Default:** `./` (current directory) - -**Examples:** -- `--pack-location ./policies` - Validate policies from `./policies` directory -- `--pack-location /path/to/policy-pack` - Validate policies from absolute path - -### `--facts` - -Provides facts as a JSON string for type checking. This helps validate that fact declarations match expected types. - -```bash -sentrie validate user_management/user_access --facts '{"user":{"role":"admin","status":"active"}}' -``` +| Name | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| TARGET | string | Yes | `namespace/policy` or `namespace/policy/rule`. Namespace and policy must exist; rule is optional (used for context). | +| `--pack-location` | path | No | Directory containing the policy pack. Default: `./`. | +| `--facts` | JSON string | No | Facts for type checking. Validates that fact types and required/optional match declarations. Default: `{}`. | -**Default:** `{}` (empty object) - -**Note:** The `--facts` flag is primarily used for type checking. The validation process will verify that: -- Fact types match their declarations -- Required facts are present -- Optional facts are correctly marked -- Shape constraints are satisfied +**Returns:** Exit 0 if validation succeeds; non-zero with error messages if pack loading, parsing, type checking, or executor creation fails. No decision output; validation only. ## Examples -### Validate a policy pack in the current directory - -```bash -sentrie validate user_management/user_access -``` - -### Validate a policy pack from a specific location - -```bash -sentrie validate com/example/auth/access_control \ - --pack-location ./policy-pack -``` - -### Validate with facts for type checking - -```bash -sentrie validate user_management/user_access \ - --facts '{"user":{"role":"admin","status":"active"}}' -``` - -### Validate a nested namespace policy - -```bash -sentrie validate com/example/billing/pricing \ - --pack-location ./policies -``` - -## What Gets Validated - -The `validate` command checks: +### Basic Usage -### 1. Pack File Structure -- Validates `sentrie.pack.toml` exists and is correctly formatted -- Checks pack metadata (name, version, schema version) - -### 2. Policy File Syntax -- Parses all `.sentrie` files in the pack -- Validates namespace declarations -- Checks policy and rule syntax -- Verifies shape definitions - -### 3. Type System -- Validates type annotations on facts, variables, and expressions -- Checks shape field types and constraints -- Verifies constraint validations (min, max, length, etc.) - -### 4. References -- Validates namespace, policy, and rule references -- Checks that imported rules exist -- Verifies exported shapes are accessible -- Validates rule imports and exports - -### 5. Executor Creation -- Attempts to create a runtime executor -- Validates that all TypeScript modules can be loaded -- Checks that all dependencies are resolvable - -## Exit Codes - -- **0** - Validation succeeded -- **1** - Validation failed (with error messages) - -## Error Messages - -Common validation errors include: - -- **Pack loading errors**: Invalid pack file structure or missing pack file -- **Syntax errors**: Invalid policy syntax or grammar violations -- **Type errors**: Type mismatches or invalid type annotations -- **Reference errors**: Missing namespaces, policies, or rules -- **Constraint violations**: Values that don't satisfy shape constraints -- **Module errors**: Missing or invalid TypeScript modules - -## Use Cases - -### Pre-deployment Validation - -Validate policies before deploying to production: +Validate a policy in the current directory: ```bash -sentrie validate com/example/auth/access_control \ - --pack-location ./policies +sentrie validate com/example/user_management/user_access ``` -### CI/CD Integration - -Use in CI/CD pipelines to catch errors early: +Validate from a specific pack path: ```bash -#!/bin/bash -if ! sentrie validate com/example/auth/access_control; then - echo "Validation failed!" - exit 1 -fi +sentrie validate com/example/auth/access_control --pack-location ./policy-pack ``` -### Type Checking +### Advanced Usage -Validate that facts match expected types: +Validate with facts for type checking: ```bash -sentrie validate user_management/user_access \ +sentrie validate com/example/user_management/user_access \ --facts '{"user":{"role":"admin","status":"active"}}' ``` -## Differences from `exec` +## Behavior & Constraints -| Feature | `validate` | `exec` | -|---------|-----------|--------| -| Executes policies | ❌ No | ✅ Yes | -| Validates structure | ✅ Yes | ✅ Yes | -| Validates types | ✅ Yes | ✅ Yes | -| Shows results | ❌ No | ✅ Yes | -| Output format | Text errors | Table/JSON | -| Use case | Pre-deployment | Testing/Execution | +- **Checks performed:** Pack file structure (`sentrie.pack.toml`), policy file parsing and syntax, namespace/policy/rule references, type annotations and shape constraints, and executor creation (including TypeScript module loading). Does not execute rules. +- **Facts:** When `--facts` is provided, validates that fact types and required/optional match policy declarations and that values satisfy shapes. +- **Output:** Errors printed to stderr; no table or JSON decision output. -## See Also +## Constraints & Edge Cases -- [CLI Reference](/cli-reference) - Complete CLI documentation -- [Policy Language Reference](/reference) - Learn about writing policies -- [Structure of a Policy Pack](/structure-of-a-policy-pack/overview) - Learn about pack structure +- Invalid or missing TARGET (unknown namespace/policy) → error. +- Pack load failure, parse error, type error, or reference error → non-zero exit; fix pack and re-run. +- `--facts` must be valid JSON; invalid JSON → error. +- Differs from `exec`: validate does not run policies or produce decision output; use `exec` for execution and results. See [sentrie exec](/cli-reference/exec) for execution. diff --git a/src/content/docs/deployment-operations/cli-reference.md b/src/content/docs/deployment-operations/cli-reference.md deleted file mode 100644 index ced4103..0000000 --- a/src/content/docs/deployment-operations/cli-reference.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: CLI Reference -description: "Sentrie CLI: exec, init, serve, validate. Syntax, options, and behavior." ---- - - -The Sentrie CLI provides commands to execute policies, initialize packs, serve an HTTP API, and validate packs. All commands support `--help`, `--version`, `--debug`, `--log-level`. - -## Syntax - -```bash -sentrie exec [OPTIONS] -sentrie init -sentrie serve [OPTIONS] [policy-pack] -sentrie validate [OPTIONS] -``` - -## Concepts - -| Command | Required | Description | -| :--- | :--- | :--- | -| `exec` FQN | Yes | `namespace/policy` or `namespace/policy/rule`. Execute rule(s) with facts. | -| `init` pack-name | Yes | Create a new policy pack in the current directory. | -| `serve` | No | Start HTTP server. Optional positional: pack directory. Default pack: `./`. | -| `validate` FQN | Yes | Validate pack structure and types. | - -**exec options:** `--pack-location` (default `./`), `--facts` (JSON string), `--fact-file` (path), `--output` (`table` \| `json`). - -**serve options:** `--port` (default `7529`), `--pack-location` (default `./`), `--listen` (default `["local"]`: `local` \| `all` \| IP list). - -**validate options:** `--pack-location` (default `./`), `--facts` (JSON for type checking). - -**Returns:** exec: exit 0 with decision output; non-zero on error. serve: runs until SIGINT/SIGTERM. validate: exit 0 if valid. - -## Examples - -### Basic Usage - -```bash -sentrie exec com/example/auth/user/allow --facts '{"user":{"role":"admin"}}' -sentrie serve --port 8080 -sentrie validate com/example/auth/user -``` - -### Advanced Usage - -```bash -sentrie exec com/example/auth/user --fact-file ./facts.json --output json -sentrie serve --listen all --pack-location /path/to/pack -``` - -## Behavior & Constraints - -- Global options: `--help`, `--version`, `--debug`, `--log-level` (DEBUG, INFO, WARN, ERROR). Facts for exec must be valid JSON; keys match policy fact names/aliases. -- serve: Loads pack from `--pack-location` or positional arg; policy/JS errors prevent startup. Graceful shutdown on SIGINT/SIGTERM. - -## Constraints & Edge Cases - -- Missing required fact for exec causes error. Invalid FQN or pack path causes error. Port in use: use another `--port`. See [Running as a Service](/deployment-operations/running-as-service) for HTTP API. diff --git a/src/content/docs/deployment-operations/running-as-service.md b/src/content/docs/deployment-operations/running-as-service.md index ab4bf87..05b47a0 100644 --- a/src/content/docs/deployment-operations/running-as-service.md +++ b/src/content/docs/deployment-operations/running-as-service.md @@ -73,7 +73,7 @@ Evaluates a policy or rule. `{target...}` = path segments: `namespace/policy` or | `attachments` | object | Exported attachments for that rule. | | `error` | string | Non-empty if execution failed. | -## Concepts +## Endpoints | Endpoint | Method | Path | Body | | :--- | :--- | :--- | :--- | diff --git a/src/content/docs/extensibility/writing-custom-typescript-modules.md b/src/content/docs/extensibility/writing-custom-typescript-modules.md index dc33879..2791291 100644 --- a/src/content/docs/extensibility/writing-custom-typescript-modules.md +++ b/src/content/docs/extensibility/writing-custom-typescript-modules.md @@ -15,7 +15,7 @@ use { fn1 } from "@local/path/to/module" [ as alias ] Built-in: `use { fn1 } from @sentrie/module` (no quotes). Local: quoted path or `@local/...`; resolved relative to current file or pack root. -## Concepts +## Parameters | Element | Type | Required | Description | | :--- | :--- | :--- | :--- | diff --git a/src/content/docs/getting-started/quick-start.md b/src/content/docs/getting-started/quick-start.md index 5f74bba..b2831f1 100644 --- a/src/content/docs/getting-started/quick-start.md +++ b/src/content/docs/getting-started/quick-start.md @@ -27,7 +27,7 @@ Verify: sentrie --version ``` -## Concepts +## Parameters | Step | Required | Description | | :--- | :--- | :--- | diff --git a/src/content/docs/getting-started/running-your-policy.md b/src/content/docs/getting-started/running-your-policy.md index 60a00b9..0a16e0f 100644 --- a/src/content/docs/getting-started/running-your-policy.md +++ b/src/content/docs/getting-started/running-your-policy.md @@ -8,19 +8,19 @@ Use `sentrie exec` to evaluate one or all exported rules in a policy. You supply ## Syntax ```bash -sentrie exec TARGET [ --facts JSON ] [ --pack PATH ] +sentrie exec TARGET [ --facts JSON ] [ --pack-location PATH ] ``` -- **TARGET:** `namespace/policy` (all exported rules) or `namespace/policy/rule` (single rule). Run from pack directory or use `--pack PATH`. +- **TARGET:** `namespace/policy` (all exported rules) or `namespace/policy/rule` (single rule). Run from pack directory or use `--pack-location PATH`. - **Facts:** JSON object. Keys are fact names (or aliases). Required facts must be present; optional facts may be omitted if they have defaults. -## Concepts +## Options | Concept | Required | Description | | :--- | :--- | :--- | | Target | Yes | `namespace/policy` or `namespace/policy/rule`. Slashes match namespace and policy/rule identifiers. | | Facts | Depends | JSON object. Required if the policy declares required facts. Keys match fact names (or aliases). | -| Pack path | No | Default: current directory. Use `--pack PATH` to point at a different pack root. | +| Pack path | No | Default: current directory. Use `--pack-location PATH` to point at a different pack root. | **Returns:** Exit code 0 on success; non-zero on evaluation or CLI error. Rule names and decision values are printed to stdout. Output includes namespace, policy, rules (match and value), and optional attachments. @@ -73,14 +73,14 @@ Values: ### Using a specific pack directory ```bash -sentrie exec com/example/user_management/user_access --pack /path/to/my-pack --facts '{"user": {"role": "user", "status": "active"}}' +sentrie exec com/example/user_management/user_access --pack-location /path/to/my-pack --facts '{"user": {"role": "user", "status": "active"}}' ``` ## Behavior & Constraints - **Target format:** `namespace/policy` or `namespace/policy/rule`. Namespace and policy/rule must exist; rule must be exported. - **Facts JSON:** Keys must match fact names (or aliases) in the policy. Types must satisfy the declared shapes. Required facts missing → evaluation error. -- **Working directory:** If `--pack` is omitted, the current directory is used as the pack root (must contain `*.sentrie` and optionally `sentrie.pack.toml`). +- **Working directory:** If `--pack-location` is omitted, the current directory is used as the pack root (must contain `*.sentrie` and optionally `sentrie.pack.toml`). - **Exit code:** 0 on success; non-zero on parse error, missing fact, or evaluation failure. ## Constraints & Edge Cases diff --git a/src/content/docs/language-concepts/pattern-matching-conditionals.md b/src/content/docs/language-concepts/pattern-matching-conditionals.md index 868f001..350fb00 100644 --- a/src/content/docs/language-concepts/pattern-matching-conditionals.md +++ b/src/content/docs/language-concepts/pattern-matching-conditionals.md @@ -18,7 +18,7 @@ Sentrie uses trinary logic (`true`, `false`, `unknown`) and provides the ternary **Emptiness:** `value is empty` | `value is not empty` -## Concepts +## Reference | Construct | Left | Right | Description | | :----------- | :------------------ | :-------------------- | :---------------------------------------------------------------- | diff --git a/src/content/docs/language-concepts/policy-composition.md b/src/content/docs/language-concepts/policy-composition.md index 76fb705..a06f3a9 100644 --- a/src/content/docs/language-concepts/policy-composition.md +++ b/src/content/docs/language-concepts/policy-composition.md @@ -24,7 +24,7 @@ rule localName = import decision of ruleName with targetFact2 as expression2 ``` -## Concepts +## Reference | Element | Type | Required | Description | | :--- | :--- | :--- | :--- | diff --git a/src/content/docs/reference/arithmetic-operations.md b/src/content/docs/reference/arithmetic-operations.md index dbdcff9..822b8c1 100644 --- a/src/content/docs/reference/arithmetic-operations.md +++ b/src/content/docs/reference/arithmetic-operations.md @@ -18,7 +18,7 @@ expr % expr Unary: `+ expr` | `- expr` -## Concepts +## Reference | Operator | Description | Result | | :--- | :--- | :--- | diff --git a/src/content/docs/reference/boolean-operations.md b/src/content/docs/reference/boolean-operations.md index 3d902ea..55e392c 100644 --- a/src/content/docs/reference/boolean-operations.md +++ b/src/content/docs/reference/boolean-operations.md @@ -1,34 +1,38 @@ --- title: Boolean Operations -description: Logical (and, or, xor, not) and comparison (==, !=, <, <=, >, >=) operators. +description: Logical (and, or, xor, not), comparison (==, !=, <, <=, >, >=), pattern (matches), and conditional operators. --- - -Logical and comparison operators produce trinary or bool results. Operands are trinary; comparisons and logical ops follow [trinary](/reference/trinary) semantics. +Logical, comparison, and pattern operators produce trinary or bool results. Operands are trinary for logical/comparison; [trinary](/reference/trinary) semantics apply. Pattern operator `matches` is string-only. ## Syntax -**Logical:** `and` | `or` | `xor` | `not` | `!` +**Logical (binary):** `and` | `or` | `xor` + +**Negation (unary):** `not expr` | `! expr` — one operand; result is trinary NOT. **Comparison:** `==` | `!=` | `is` | `is not` | `<` | `<=` | `>` | `>=` +**Pattern:** `string matches pattern` (pattern is a regex string) + **Conditional:** `condition ? trueValue : falseValue` | `expr ?: default` -## Concepts +## Reference -| Operator | Description | Returns | -| :--- | :--- | :--- | -| `and` | Logical AND (Kleene) | trinary | -| `or` | Logical OR (Kleene) | trinary | -| `xor` | Logical XOR | trinary | -| `not`, `!` | Logical NOT | trinary | -| `==`, `is` | Equality | trinary | -| `!=`, `is not` | Inequality | trinary | -| `<`, `<=`, `>`, `>=` | Ordering | trinary | -| `? :` | Ternary | type of chosen branch | -| `?:` | Elvis (default if not truthy) | type | +| Operator | Description | Returns | +| :------------------- | :-------------------------------------------------------------------------- | :-------------------- | +| `and` | Logical AND (Kleene) | trinary | +| `or` | Logical OR (Kleene) | trinary | +| `xor` | Logical XOR | trinary | +| `not`, `!` | Logical NOT (unary). Single operand; converted to trinary then negated. | trinary | +| `==`, `is` | Equality | trinary | +| `!=`, `is not` | Inequality | trinary | +| `<`, `<=`, `>`, `>=` | Ordering | trinary | +| `matches` | String matches regex (left: string, right: pattern). Invalid regex → error. | bool | +| `? :` | Ternary | type of chosen branch | +| `?:` | Elvis (default if not truthy) | type | -**Returns:** Trinary or the selected value. Non-truthy for Elvis: false, unknown, null, 0, "", empty collection. +**Returns:** Trinary or the selected value. Non-truthy for Elvis: `false`, `unknown`, `null`, `0`, `""`, `empty collection`. ## Examples @@ -37,21 +41,28 @@ Logical and comparison operators produce trinary or bool results. Operands are t ```sentrie let a: bool = true and false let b: bool = age >= 18 -let c: string = age >= 18 ? "adult" : "minor" -let d: string = user.name ?: "Anonymous" +let c: bool = not (user.role in allowed_roles) +let d: string = age >= 18 ? "adult" : "minor" +let e: string = user.name ?: "Anonymous" ``` ### Advanced Usage ```sentrie -let e: bool = user.role == "admin" or (user.role == "user" and user.status == "active") +let f: bool = user.role == "admin" or (user.role == "user" and user.status == "active") +let g: bool = email matches `^[a-z]+@[a-z]+\\.com$` +let h: bool = not (role in ["guest"]) and status == "active" ``` ## Behavior & Constraints +- **not / !:** Unary prefix; one operand. Operand is converted to trinary ([trinary](/reference/trinary)) then negated (true↔false, unknown stays unknown). - Short-circuit: `and`/`or` evaluate left-to-right; right side may be skipped. Ternary evaluates only the chosen branch. - Comparison: both sides must be comparable; result is trinary. +- **matches:** Left and right must be strings. Right is interpreted as a [Go regexp](https://pkg.go.dev/regexp) pattern. Invalid pattern string causes an evaluation error. ## Constraints & Edge Cases +- **not / !:** `not unknown` yields `unknown`. Use `not expr` or `! expr`; no space required after `!`. - `unknown` in logical ops propagates per Kleene. Equality with `unknown` yields `unknown`. +- **matches:** Non-string operands or invalid regex → error. For membership in collections or substrings, use [in](/reference/membership-operations) or [contains](/reference/membership-operations). diff --git a/src/content/docs/reference/collection-operations.md b/src/content/docs/reference/collection-operations.md index 061e9b2..331f83a 100644 --- a/src/content/docs/reference/collection-operations.md +++ b/src/content/docs/reference/collection-operations.md @@ -20,7 +20,7 @@ distinct collection Index parameter is optional in some forms. For maps, element is key-value or value depending on operation. -## Concepts +## Reference | Operation | Input | Output | Description | | :--- | :--- | :--- | :--- | diff --git a/src/content/docs/reference/constraints.md b/src/content/docs/reference/constraints.md index 396e3d4..d8ebd49 100644 --- a/src/content/docs/reference/constraints.md +++ b/src/content/docs/reference/constraints.md @@ -14,7 +14,7 @@ type @constraint1(args) @constraint2 Examples: `number @min(0) @max(100)`, `string @email`, `list[string @one_of("a","b")]`. -## Concepts +## Reference | Category | Constraints (examples) | | :--- | :--- | diff --git a/src/content/docs/reference/facts.md b/src/content/docs/reference/facts.md index 902839f..57c52b1 100644 --- a/src/content/docs/reference/facts.md +++ b/src/content/docs/reference/facts.md @@ -13,7 +13,7 @@ fact name : type [ as alias ] [ default expr ] -- required fact name? : type [ as alias ] [ default expr ] -- optional ``` -## Concepts +## Reference | Part | Type | Required | Description | | :------------- | :-------------- | :------- | :-------------------------------------------------- | diff --git a/src/content/docs/reference/functions.md b/src/content/docs/reference/functions.md index 2404008..4926050 100644 --- a/src/content/docs/reference/functions.md +++ b/src/content/docs/reference/functions.md @@ -15,7 +15,7 @@ alias.functionName(arg1, arg2, ...) Import: `use { fn1, fn2 } from source [ as alias ]` -## Concepts +## Reference | Element | Type | Required | Description | | :--- | :--- | :--- | :--- | diff --git a/src/content/docs/reference/let.md b/src/content/docs/reference/let.md index 8ca445f..ba9cf99 100644 --- a/src/content/docs/reference/let.md +++ b/src/content/docs/reference/let.md @@ -13,7 +13,7 @@ let name = expr let name : type = expr ``` -## Concepts +## Reference | Part | Type | Required | Description | | :--- | :--- | :--- | :--- | diff --git a/src/content/docs/reference/membership-operations.md b/src/content/docs/reference/membership-operations.md new file mode 100644 index 0000000..477e302 --- /dev/null +++ b/src/content/docs/reference/membership-operations.md @@ -0,0 +1,63 @@ +--- +title: Membership Operations +description: "Containment operators: in and contains (list, map, string)." +--- + + +`in` and `contains` test whether a value is contained in a collection or string. Both return a boolean. `needle in haystack` and `haystack contains needle` are equivalent; use whichever reads more naturally. + +## Syntax + +```text +needle in haystack +haystack contains needle +``` + +- **in:** Left = needle, right = haystack. True if haystack contains needle. +- **contains:** Left = haystack, right = needle. True if haystack contains needle. + +## Reference + +| Operator | Left | Right | Description | +| :--- | :--- | :--- | :--- | +| `in` | needle | haystack | True if haystack (string, list, or map) contains needle. | +| `contains` | haystack | needle | Same as above; argument order reversed. | + +**String:** Haystack and needle are strings. True if needle is a non-empty substring of haystack. + +**List:** Haystack is a list; needle is any value. True if any element equals needle (by `==`). + +**Map:** Haystack is a map. If needle is a string, true if that key exists. If needle is a map, true if haystack has all keys from needle and the corresponding values are equal. + +**Returns:** bool. No trinary; invalid or unsupported types yield false (or key-mismatch for map subset). + +## Examples + +### Basic Usage + +```sentrie +let in_str: bool = "foo" in "xfoobar" +let has_role: bool = "admin" in user.roles +let has_guest: bool = roles contains "guest" +``` + +### Advanced Usage + +```sentrie +let sub: bool = "admin" in roles +let key_exists: bool = "plan" in subscription +let subset: bool = {"a": 1, "b": 2} contains subscription +``` + +## Behavior & Constraints + +- **String:** Needle must be non-empty string for a true result; empty needle → false. Substring is contiguous. +- **List:** Element-wise equality; no deep comparison of nested structures beyond `equals`. +- **Map (key):** Needle string checks key presence only. +- **Map (subset):** Needle map: every key in needle must exist in haystack with the same value; extra keys in haystack are allowed. + +## Constraints & Edge Cases + +- Type mismatch (e.g. needle not string/list/map when haystack is map, or haystack not string/list/map) → false. +- For regex matching on strings, use [matches](/reference/boolean-operations) in Boolean Operations. +- Empty list or empty string haystack → false. For string containment, needle must be non-empty. diff --git a/src/content/docs/reference/namespaces.md b/src/content/docs/reference/namespaces.md index 4b6c57b..d72b698 100644 --- a/src/content/docs/reference/namespaces.md +++ b/src/content/docs/reference/namespaces.md @@ -14,7 +14,7 @@ namespace fully/qualified/name FQN is slash-separated identifiers (e.g. `com/example/auth`, `mycompany/policies/security`). -## Concepts +## Reference | Element | Type | Required | Description | | :--- | :--- | :--- | :--- | diff --git a/src/content/docs/reference/policies.md b/src/content/docs/reference/policies.md index 0098766..1a21cd7 100644 --- a/src/content/docs/reference/policies.md +++ b/src/content/docs/reference/policies.md @@ -18,7 +18,7 @@ policy IDENT { } ``` -## Concepts +## Reference | Statement | Required | Description | | :--- | :--- | :--- | diff --git a/src/content/docs/reference/precedence.md b/src/content/docs/reference/precedence.md index d9c72a6..17568b6 100644 --- a/src/content/docs/reference/precedence.md +++ b/src/content/docs/reference/precedence.md @@ -10,7 +10,7 @@ Operators are evaluated in order of precedence (highest first). Same precedence Expressions combine operators; precedence determines binding. Ternary `? :` is right-associative. -## Concepts +## Reference | Precedence | Operators | Description | | :--- | :--- | :--- | diff --git a/src/content/docs/reference/rules.md b/src/content/docs/reference/rules.md index e0ed5dc..f36fee6 100644 --- a/src/content/docs/reference/rules.md +++ b/src/content/docs/reference/rules.md @@ -12,7 +12,7 @@ A rule defines a decision: an optional `when` condition, an optional `default`, rule IDENT = [ default expr ] [ when expr ] { stmt* yield expr } ``` -## Concepts +## Reference | Part | Type | Required | Description | | :--- | :--- | :--- | :--- | diff --git a/src/content/docs/reference/security-and-permissions.md b/src/content/docs/reference/security-and-permissions.md index c13f6d4..77b7a81 100644 --- a/src/content/docs/reference/security-and-permissions.md +++ b/src/content/docs/reference/security-and-permissions.md @@ -15,7 +15,7 @@ net = ["host1.com", "host2.com"] env = ["VAR1", "VAR2"] ``` -## Concepts +## Reference | Key | Type | Description | | :--- | :--- | :--- | diff --git a/src/content/docs/reference/shapes.md b/src/content/docs/reference/shapes.md index 105a4aa..db6af92 100644 --- a/src/content/docs/reference/shapes.md +++ b/src/content/docs/reference/shapes.md @@ -21,7 +21,7 @@ shape Name { **Composition:** `shape Child with Base { ... }` -## Concepts +## Reference | Modifier | Description | | :--- | :--- | diff --git a/src/content/docs/reference/trinary.md b/src/content/docs/reference/trinary.md index 1782404..3a63685 100644 --- a/src/content/docs/reference/trinary.md +++ b/src/content/docs/reference/trinary.md @@ -30,27 +30,27 @@ let c = not unknown -- unknown ### Kleene AND -| AND | true | false | unknown | +| **AND** | true | false | unknown | | --- | --- | --- | --- | -| true | true | false | unknown | -| false | false | false | false | -| unknown | unknown | false | unknown | +| **true** | true | false | unknown | +| **false** | false | false | false | +| **unknown** | unknown | false | unknown | ### Kleene OR -| OR | true | false | unknown | +| **OR** | true | false | unknown | | --- | --- | --- | --- | -| true | true | true | true | -| false | true | false | unknown | -| unknown | true | unknown | unknown | +| **true** | true | true | true | +| **false** | true | false | unknown | +| **unknown** | true | unknown | unknown | ### NOT -| Input | Output | +| **Input** | Output | | --- | --- | -| true | false | -| false | true | -| unknown | unknown | +| **true** | false | +| **false** | true | +| **unknown** | unknown | ## Behavior & Constraints diff --git a/src/content/docs/reference/types-and-values.md b/src/content/docs/reference/types-and-values.md index 2aed953..1c36bf1 100644 --- a/src/content/docs/reference/types-and-values.md +++ b/src/content/docs/reference/types-and-values.md @@ -16,7 +16,7 @@ Sentrie provides primitive types (`number`, `string`, `trinary`, `bool`, `docume List index: `expr[number]`. Map index: `expr[string]` or `expr.key`. -## Concepts +## Reference | Type | Description | | :--- | :--- | diff --git a/src/content/docs/reference/typescript_modules/index.md b/src/content/docs/reference/typescript_modules/index.md index 6d371d4..99a94cd 100644 --- a/src/content/docs/reference/typescript_modules/index.md +++ b/src/content/docs/reference/typescript_modules/index.md @@ -14,7 +14,7 @@ use { fn1, fn2 } from @sentrie/module [ as alias ] Built-in modules: `@sentrie/module` (no quotes). Default alias is the last path segment (e.g. `time` for `@sentrie/time`). -## Concepts +## Reference | Element | Type | Required | Description | | :--- | :--- | :--- | :--- | diff --git a/src/content/docs/reference/typescript_modules/sentrie/json.md b/src/content/docs/reference/typescript_modules/sentrie/json.md index e899611..e513c78 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/json.md +++ b/src/content/docs/reference/typescript_modules/sentrie/json.md @@ -13,7 +13,7 @@ use { isValid } from @sentrie/json [ as alias ] alias.isValid(str) ``` -## Concepts +## Reference | Name | Type | Required | Description | | :---- | :----- | :------- | :-------------------------- | diff --git a/src/content/docs/reference/typescript_modules/sentrie/uuid.md b/src/content/docs/reference/typescript_modules/sentrie/uuid.md index fd094ad..1db33d5 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/uuid.md +++ b/src/content/docs/reference/typescript_modules/sentrie/uuid.md @@ -15,7 +15,7 @@ alias.v6() alias.v7() ``` -## Concepts +## Reference | Function | Parameters | Required | Description | | :------- | :--------- | :------- | :------------------------------------------------- | From 6cd1b4679d8677b4fa374044fcb811f940f92863 Mon Sep 17 00:00:00 2001 From: Binaek Sarkar Date: Sat, 21 Feb 2026 18:58:25 +0530 Subject: [PATCH 07/21] Add new section for writing a policy in the quick start guide --- .../docs/getting-started/quick-start.md | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/src/content/docs/getting-started/quick-start.md b/src/content/docs/getting-started/quick-start.md index b2831f1..5e03762 100644 --- a/src/content/docs/getting-started/quick-start.md +++ b/src/content/docs/getting-started/quick-start.md @@ -27,13 +27,44 @@ Verify: sentrie --version ``` +## Writing your first policy + +```bash +sentrie init my-pack +``` + +This will create a new policy pack in the current directory. + +```bash +$ echo 'namespace auth { \ + policy user { \ + rule allow = default false { \ + yield true \ + } \ + } \ +}' > my-pack/policy.sentrie +``` + +```bash +sentrie exec auth/user/allow + +Namespace: auth +Policy: user + +Rules: + ✓ allow: ✓ True + +Values: + ✓ allow: true +``` + ## Parameters -| Step | Required | Description | -| :--- | :--- | :--- | -| Policy pack path | Yes | Directory containing `*.sentrie` and optional pack manifest. | -| Target | Yes | `namespace/policy` or `namespace/policy/rule`. | -| Facts | Depends | JSON object of fact names to values; required if the policy declares required facts. | +| Step | Required | Description | +| :--------------- | :------- | :----------------------------------------------------------------------------------- | +| Policy pack path | No | Directory containing `*.sentrie.toml` pack manifest. Default: current directory. | +| Target | Yes | `namespace/policy` or `namespace/policy/rule`. | +| Facts | Depends | JSON object of fact names to values; required if the policy declares required facts. | **Returns:** Exit code 0 on success; non-zero on evaluation or CLI error. Decision output is printed to stdout. From 3bb90154669f3b828bb960d3099a7a86cd3908b5 Mon Sep 17 00:00:00 2001 From: Binaek Sarkar Date: Sat, 21 Feb 2026 23:20:51 +0530 Subject: [PATCH 08/21] Update policy documentation to clarify the requirement of the Policy block --- src/content/docs/getting-started/writing-your-first-policy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/getting-started/writing-your-first-policy.md b/src/content/docs/getting-started/writing-your-first-policy.md index 327fe48..4233679 100644 --- a/src/content/docs/getting-started/writing-your-first-policy.md +++ b/src/content/docs/getting-started/writing-your-first-policy.md @@ -29,7 +29,7 @@ policy IDENT { | :-------- | :------- | :------------------------------------------------------------------------------------------------------------------ | | Namespace | Yes | Single `namespace SLUG` per file; first statement. Slash-separated (e.g. `com/example/app`). | | Shape | No | Data model for facts. Required/optional fields: `field!`, `field?`. | -| Policy | Yes | Named block containing facts, rules, and exports. | +| Policy | No | Named block containing facts, rules, and exports. | | Fact | No | Input to the policy. Required by default; `?` makes optional. Only optional facts may have `default`. Non-nullable. | | Rule | Yes (≥1) | Block that yields a decision. May reference other rules in the same policy. | | Export | Yes (≥1) | `export decision of ruleName` makes the rule callable via CLI/API and importable. | From fb05c732d948101f52c350ba8f470bb354cd2ae8 Mon Sep 17 00:00:00 2001 From: Binaek Sarkar Date: Sat, 21 Feb 2026 23:34:56 +0530 Subject: [PATCH 09/21] Enhance documentation clarity and consistency across multiple sections --- docs-template.md | 51 ++++++------ src/content/docs/cli-reference/exec.md | 55 ++++++------ src/content/docs/cli-reference/init.md | 46 +++++----- src/content/docs/cli-reference/serve.md | 51 ++++++------ src/content/docs/cli-reference/validate.md | 46 +++++----- .../running-as-service.md | 11 +-- .../writing-custom-typescript-modules.md | 13 +-- .../docs/getting-started/enforcement.md | 66 ++++++++------- .../docs/getting-started/installation.md | 70 +++++++++++----- .../docs/getting-started/introduction.md | 5 +- .../docs/getting-started/quick-start.md | 83 ++++++------------- .../getting-started/running-your-policy.md | 47 +++++------ .../writing-your-first-policy.md | 60 ++++++++------ .../pattern-matching-conditionals.md | 29 ++++--- .../language-concepts/policy-composition.md | 13 +-- .../language-concepts/type-system-shapes.md | 11 +-- .../docs/reference/arithmetic-operations.md | 13 +-- .../docs/reference/boolean-operations.md | 70 +++++++++------- .../docs/reference/collection-operations.md | 13 +-- src/content/docs/reference/constraints.md | 13 +-- src/content/docs/reference/facts.md | 48 ++++++----- src/content/docs/reference/functions.md | 13 +-- src/content/docs/reference/index.md | 4 +- src/content/docs/reference/let.md | 17 ++-- .../docs/reference/membership-operations.md | 48 ++++++----- src/content/docs/reference/namespaces.md | 42 +++++----- src/content/docs/reference/policies.md | 39 +++++---- src/content/docs/reference/precedence.md | 13 +-- src/content/docs/reference/rules.md | 17 ++-- .../reference/security-and-permissions.md | 13 +-- src/content/docs/reference/shapes.md | 13 +-- src/content/docs/reference/trinary.md | 68 ++++++++------- .../docs/reference/types-and-values.md | 13 +-- .../reference/typescript_modules/index.md | 13 +-- .../typescript_modules/sentrie/collection.md | 5 +- .../typescript_modules/sentrie/crypto.md | 5 +- .../typescript_modules/sentrie/encoding.md | 5 +- .../typescript_modules/sentrie/hash.md | 5 +- .../typescript_modules/sentrie/js.md | 5 +- .../typescript_modules/sentrie/json.md | 13 +-- .../typescript_modules/sentrie/jwt.md | 5 +- .../typescript_modules/sentrie/net.md | 5 +- .../typescript_modules/sentrie/regex.md | 5 +- .../typescript_modules/sentrie/semver.md | 5 +- .../typescript_modules/sentrie/time.md | 5 +- .../typescript_modules/sentrie/url.md | 5 +- .../typescript_modules/sentrie/uuid.md | 13 +-- 47 files changed, 635 insertions(+), 573 deletions(-) diff --git a/docs-template.md b/docs-template.md index 8dfb1ae..28eb907 100644 --- a/docs-template.md +++ b/docs-template.md @@ -1,49 +1,50 @@ -# [Feature / Function Name] +# [Feature Name] -[A brief, 1-2 sentence introductory paragraph. Write this for the human. Explain what the feature is and the primary reason a developer would reach for it. Keep it factual and avoid marketing fluff, but let it flow naturally.] +[A short, conversational paragraph explaining what this is and the exact problem it solves. Write as if you are explaining it to a colleague. Focus on the "why"—e.g., "When you need to guarantee the exact structure of an incoming payload before evaluating it, use Shapes."] -## Syntax +Here is the basic syntax: ```typescript // The formal definition or type signature ``` -## Parameters +## Configuration & Arguments -| Name | Type | Required | Description | -| :---------- | :----- | :------- | :---------------------------------------------------- | -| `paramName` | `Type` | Yes/No | A clear, concise explanation of what this input does. | +[A brief, friendly sentence setting up the table, e.g., "You can customize the behavior using the following parameters:"] -**Returns:** `ReturnType` - [A brief description of the output, e.g., "A boolean indicating whether the constraint was met."] +| Argument | Type | Required | What it does | +| :---------- | :----- | :------- | :-------------------------------------------- | +| `paramName` | `Type` | Yes/No | A plain-English explanation of this argument. | -## Examples +**Returns:** `ReturnType` — [Explain what the developer gets back in plain English, e.g., "A validated object, or it aborts the evaluation if constraints fail."] -### Basic Usage +--- -[One sentence explaining what this example demonstrates.] +## Examples in Action + +### [Scenario 1: e.g., Validating a standard user profile] + +[One to two sentences explaining the real-world context of this example. Why would a developer actually write this?] ```typescript -// Minimum reproducible code block. Use realistic variable names. +// Clean, copy-pasteable code using realistic variable names +// like userAge or billingAddress instead of foo and bar. ``` -### Advanced Usage +### [Scenario 2: e.g., Handling complex nested objects] -[One sentence explaining the complexity or edge case shown here.] +[Context for the advanced use case. What edge case or complexity does this solve?] ```typescript -// Example showing composition, complex input, or error handling. +// Advanced example code showing composition or error handling. ``` -## Behavior & Constraints - -> **Note:** [Optional: A high-visibility callout for the most critical thing a human needs to know before using this feature.] +--- -- **[Constraint 1]:** e.g., Failing constraint validation will abort evaluation immediately. -- **[Limitation]:** e.g., Does not support dynamic key generation. All keys must be explicitly declared. -- **[Compatibility]:** e.g., Cannot be used in conjunction with [Feature Y]. +## Good to Know -## Constraints & Edge Cases +Before you implement this, keep a few boundaries in mind to ensure predictable execution: -- [Limitation 1: e.g., "Failing constraint validation will abort evaluation immediately."] -- [Limitation 2: e.g., "Does not support dynamic key generation."] -- [Mutual Exclusions: e.g., "Cannot be used in conjunction with X."] +- **[Constraint]:** [e.g., "Sentrie evaluates these strictly. If an incoming payload includes keys not defined in your Shape, the evaluation will fail."] +- **[Performance tip]:** [e.g., "If you are validating deeply nested JSON, keep your recursion depth under X for optimal performance."] +- **[Edge case]:** [e.g., "Null values are only accepted if you explicitly use the `.nullable()` modifier."] diff --git a/src/content/docs/cli-reference/exec.md b/src/content/docs/cli-reference/exec.md index 616a63e..1c0d37e 100644 --- a/src/content/docs/cli-reference/exec.md +++ b/src/content/docs/cli-reference/exec.md @@ -3,51 +3,49 @@ title: "sentrie exec" description: "Execute a policy or rule with facts; output decisions to stdout." --- -`exec` loads a policy pack, runs the given rule(s) with the supplied facts, and prints decision output. Use it to test policies locally or in scripts. +When you want to run a policy from the command line—to test a rule, debug a decision, or script a one-off check—you use `sentrie exec`. It loads a policy pack, runs the rule(s) you specify with the facts you supply, and prints the decision output. Perfect for local development and CI. -## Syntax +Here is the basic syntax: ```bash sentrie exec [ --pack-location ] [ --facts ] [ --fact-file ] [ --output (table|json) ] ``` -- **TARGET:** `namespace/policy` or `namespace/policy/rule`. Required. -- **--pack-location:** Pack root directory. Default: current directory. -- **--facts:** JSON object of fact names to values. Merged with `--fact-file`; CLI overrides file. -- **--fact-file:** Path to JSON file with a top-level object of facts. -- **--output:** `table` (default) or `json`. +**TARGET** is required: `namespace/policy` or `namespace/policy/rule`. The rest are optional: pack directory, inline facts, fact file, and output format. -## Options +## Configuration & Arguments -| Name | Type | Required | Description | -| :--- | :--- | :--- | :--- | +You can customize the run using the following options: + +| Argument | Type | Required | What it does | +| :------- | :--- | :------- | :----------- | | TARGET | string | Yes | `namespace/policy` (all exported rules) or `namespace/policy/rule` (single rule). Slashes separate namespace, policy, and optional rule. | | `--pack-location` | path | No | Directory containing the policy pack. Default: `./`. | | `--facts` | JSON string | No | Inline facts. Keys match policy fact names or aliases. Overrides same keys from `--fact-file`. | | `--fact-file` | path | No | Path to JSON file; top-level object gives facts. Loaded first; `--facts` overrides. | | `--output` | enum | No | `table` or `json`. Default: `table`. | -**Returns:** Exit 0 on success; non-zero on error. Output to stdout: table (human-readable) or JSON array of decision objects (namespace, policyName, ruleName, decision.state, decision.value, attachments). +**Returns:** Exit 0 on success; non-zero on error. Output goes to stdout: table (human-readable) or JSON array of decision objects (namespace, policyName, ruleName, decision.state, decision.value, attachments). + +--- -## Examples +## Examples in Action -### Basic Usage +### Running a single rule or all rules with inline facts -Execute a single rule with inline facts: +You are testing one rule or the whole policy and your facts are small enough to pass on the command line. ```bash sentrie exec com/example/user_management/user_access/allow_user --facts '{"user":{"role":"user","status":"active"}}' ``` -Execute all exported rules in a policy: - ```bash sentrie exec com/example/user_management/user_access --facts '{"user":{"role":"admin","status":"active"}}' ``` -### Advanced Usage +### Overriding a fact file from the command line and getting JSON -Facts from file with command-line overrides; JSON output: +You have a base fact file but want to override a few keys (e.g. role) for a quick test and pipe the result to another tool. ```bash sentrie exec com/example/user_management/user_access \ @@ -56,24 +54,19 @@ sentrie exec com/example/user_management/user_access \ --output json ``` -Custom pack path and JSON output for piping: +### Running from a different pack directory and piping JSON + +Your policy pack lives in another directory and you want JSON output for scripting. ```bash sentrie exec com/example/auth/access/allow --pack-location ./policy-pack --facts '{"user":{"role":"admin"}}' --output json ``` -## Behavior & Constraints +--- -- **Facts:** Required facts must be present (in `--facts` and/or `--fact-file`). Optional facts may be omitted if they have defaults. Keys must match fact names or aliases; types must satisfy declared shapes. -- **Merge order:** Facts from `--fact-file` are loaded first; `--facts` overrides conflicting keys. -- **Output:** Table format lists namespace, policy, rules (match and value), values, and attachments. JSON format is an array of objects with namespace, policyName, ruleName, decision (state, value), attachments. -- **Decision states:** `TRUE`, `FALSE`, or `UNKNOWN` (e.g. when `when` is false and no default). +## Good to Know -## Constraints & Edge Cases +Before you rely on this in scripts or CI, keep a few boundaries in mind: -- Invalid or missing TARGET (unknown namespace/policy/rule) → error; rule must be exported. -- Missing required fact → evaluation error. -- Invalid JSON in `--facts` or in `--fact-file` → error. -- `--fact-file` path must exist and be readable. -- Pack directory must contain loadable `*.sentrie` (and optional `sentrie.pack.toml`). Policy/parse errors → error. -- For full HTTP API behavior, see [Running as a Service](/deployment-operations/running-as-service). +- **Constraint:** Required facts must be present (in `--facts` and/or `--fact-file`). Optional facts may be omitted if they have defaults. Keys must match fact names or aliases; types must satisfy declared shapes. Facts from `--fact-file` are loaded first; `--facts` overrides conflicting keys. Table output lists namespace, policy, rules, values, attachments; JSON is an array of decision objects. Decision states are `TRUE`, `FALSE`, or `UNKNOWN`. +- **Edge case:** Invalid or missing TARGET (unknown namespace/policy/rule) → error; rule must be exported. Missing required fact → evaluation error. Invalid JSON in `--facts` or `--fact-file` → error. `--fact-file` path must exist and be readable. Pack directory must contain loadable `*.sentrie`. For the full HTTP API, see [Running as a Service](/deployment-operations/running-as-service). diff --git a/src/content/docs/cli-reference/init.md b/src/content/docs/cli-reference/init.md index 15822c2..8400baa 100644 --- a/src/content/docs/cli-reference/init.md +++ b/src/content/docs/cli-reference/init.md @@ -3,58 +3,60 @@ title: "sentrie init" description: "Create a new policy pack with sentrie.pack.toml in the current or given directory." --- -`init` creates a policy pack by writing a `sentrie.pack.toml` with the given pack name. Use it to bootstrap a new pack or ensure correct pack structure. +When you are starting a new policy pack and want a valid `sentrie.pack.toml` without editing one by hand, you use `sentrie init`. It creates the pack file with the name you give and the right schema so the rest of the tooling can load the pack. -## Syntax +Here is the basic syntax: ```bash sentrie init [ --directory ] ``` -- **NAME:** Pack name (required). Must be a valid identifier: start with a letter; letters, numbers, underscores, hyphens, dots allowed; segments after a dot must start with a letter. -- **--directory:** Directory to create the pack in. Must be empty. Default: current directory. +**NAME** is required (e.g. `my-policy-pack`, `com.example.pack`). **--directory** is optional; the directory must be empty. Default is the current directory. -## Options +## Configuration & Arguments -| Name | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| NAME | string | Yes | Pack name. Valid: e.g. `my-policy-pack`, `com.example.pack`. Invalid: leading digit, leading hyphen, double dot, segment after dot starting with digit. | +You can customize where the pack is created using the following options: + +| Argument | Type | Required | What it does | +| :------- | :--- | :------- | :----------- | +| NAME | string | Yes | Pack name. Must start with a letter; letters, numbers, underscores, hyphens, dots allowed; each segment after a dot must start with a letter. Invalid: leading digit, leading hyphen, double dot. | | `--directory` | path | No | Directory for the pack. Must be empty. Default: `./`. | **Returns:** Exit 0 on success. Creates `sentrie.pack.toml` with `[schema] version = 1`, `[pack] name = ""`, `version = "0.0.1"`. No other files are created. -## Examples +--- + +## Examples in Action -### Basic Usage +### Creating a pack in the current directory -Create a pack in the current directory (must be empty): +You are in an empty folder and want to turn it into a pack with a given name. ```bash sentrie init my-policy-pack ``` -Create a pack in a new directory: +### Creating a pack in a new directory + +You want the pack to live in a new subdirectory (e.g. to keep policies separate from app code). ```bash sentrie init my-policy-pack --directory ./my-policy-pack ``` -### Advanced Usage +### Using a hierarchical pack name -Hierarchical pack name: +You are naming the pack with a dotted identifier (e.g. for org or product). ```bash sentrie init com.example.iam --directory ./iam-pack ``` -## Behavior & Constraints +--- -- **Directory:** Must exist and be empty. If the directory contains any files, the command fails. -- **Pack name:** Validated against identifier rules; invalid name produces an error and no file is written. -- **File created:** Only `sentrie.pack.toml` is written; structure matches the Sentrie pack schema. +## Good to Know -## Constraints & Edge Cases +Before you run this, keep a few boundaries in mind: -- Invalid name (e.g. `123pack`, `-mypack`, `my..pack`, `my.123pack`) → error; no pack file created. -- Non-empty directory → error (e.g. "directory is not empty - please choose a different directory"). -- `--directory` must point to an existing directory; creation of the directory is not performed by `init`. +- **Constraint:** The directory must exist and be empty. If it contains any files, the command fails. Only `sentrie.pack.toml` is written; structure matches the Sentrie pack schema. Pack name is validated; invalid name produces an error and no file is written. +- **Edge case:** Invalid name (e.g. `123pack`, `-mypack`, `my..pack`, `my.123pack`) → error; no pack file created. Non-empty directory → error (e.g. "directory is not empty - please choose a different directory"). `--directory` must point to an existing directory; `init` does not create the directory. diff --git a/src/content/docs/cli-reference/serve.md b/src/content/docs/cli-reference/serve.md index 1a65e09..9fc7580 100644 --- a/src/content/docs/cli-reference/serve.md +++ b/src/content/docs/cli-reference/serve.md @@ -3,68 +3,69 @@ title: "sentrie serve" description: "Start the HTTP server for policy evaluation; load pack from a directory." --- -`serve` starts an HTTP server that loads a policy pack and exposes a REST API for evaluating policies. Use it for local development or as the process behind a reverse proxy in production. +When you need to evaluate policies over HTTP (e.g. from a backend or API gateway), you run `sentrie serve`. It loads a policy pack from a directory and exposes a REST API so callers can POST facts and get decisions. Use it for local development or as the process behind a reverse proxy in production. -## Syntax +Here is the basic syntax: ```bash sentrie serve [ --pack-location ] [ --port ] [ --listen ... ] ``` -- **--pack-location:** Directory containing the policy pack. Default: `./`. -- **--port:** Port to listen on. Default: `7529`. -- **--listen:** Address(es) to bind (e.g. `local`, `0.0.0.0`, `127.0.0.1`). Default: `["local"]`. May be repeated. +All options are optional: pack directory defaults to the current directory, port to `7529`, and listen address to localhost. -## Options +## Configuration & Arguments -| Name | Type | Required | Description | -| :--- | :--- | :--- | :--- | +You can customize where the pack is loaded from and how the server listens using the following options: + +| Argument | Type | Required | What it does | +| :------- | :--- | :------- | :----------- | | `--pack-location` | path | No | Pack root (e.g. `*.sentrie`, optional `sentrie.pack.toml`, `*.js`). Default: `./`. | | `--port` | int | No | Listen port. Default: `7529`. | | `--listen` | string (repeatable) | No | Bind address: `local` (localhost), `0.0.0.0` (all interfaces), or specific IP. Default: `["local"]`. | **Returns:** Process runs until SIGINT/SIGTERM (graceful shutdown). Exit non-zero on startup failure (e.g. port in use, pack load or parse error). HTTP API base: `http://:`; see [Running as a Service](/deployment-operations/running-as-service) for request/response format. -## Examples +--- + +## Examples in Action -### Basic Usage +### Starting the server with defaults -Start with defaults (current directory, port 7529, localhost only): +You want to run the server on localhost with the current directory as the pack and default port. ```bash sentrie serve ``` -Custom port and pack path: +### Using a custom port and pack path + +You are developing with policies in a separate folder and want the server on a different port. ```bash sentrie serve --port 8080 --pack-location ./my-policies ``` -### Advanced Usage +### Binding to all interfaces for production-style access -Listen on all interfaces and set pack path (e.g. production-style): +You are running behind a reverse proxy or need the server reachable from other machines; you point it at a fixed pack path and listen on all interfaces. ```bash sentrie serve --pack-location /etc/sentrie/policies --listen 0.0.0.0 --port 8080 ``` -Multiple listen addresses and debug logging: +### Listening on multiple addresses with debug logging + +You want the server on specific IPs and more verbose logs for troubleshooting. ```bash sentrie serve --listen 127.0.0.1 --listen 192.168.1.100 --port 8080 --debug --log-level DEBUG ``` -## Behavior & Constraints +--- -- **Startup:** Loads pack from `--pack-location`, parses `*.sentrie`, validates, builds index, then listens. Policy or pack errors prevent startup. -- **Shutdown:** SIGINT (Ctrl+C) and SIGTERM trigger graceful shutdown; SIGKILL does not. -- **Environment:** `SENTRIE_DEBUG`, `SENTRIE_LOG_LEVEL`, `SENTRIE_PORT` can override debug, log level, and port when not set via flags. -- **Output:** All output to stdout/stderr; no file logging by default. +## Good to Know -## Constraints & Edge Cases +Before you run this in production, keep a few boundaries in mind: -- Port already in use → startup fails; choose another `--port`. -- Invalid or missing pack directory, or pack load/parse failure → startup fails; fix pack or path. -- Listening on `0.0.0.0` exposes the server on all interfaces; secure with firewall, reverse proxy, and/or auth. HTTPS and auth are not provided by `serve`; use a reverse proxy. -- Full HTTP API schema, error codes, and deployment details: [Running as a Service](/deployment-operations/running-as-service). +- **Constraint:** Startup loads the pack from `--pack-location`, parses `*.sentrie`, validates, builds the index, then listens. Policy or pack errors prevent startup. SIGINT (Ctrl+C) and SIGTERM trigger graceful shutdown; SIGKILL does not. Environment variables `SENTRIE_DEBUG`, `SENTRIE_LOG_LEVEL`, `SENTRIE_PORT` can override when not set via flags. All output goes to stdout/stderr; no file logging by default. +- **Edge case:** Port already in use → startup fails; use another `--port`. Invalid or missing pack directory or pack load/parse failure → startup fails. Listening on `0.0.0.0` exposes the server on all interfaces; secure with firewall, reverse proxy, and/or auth. HTTPS and auth are not provided by `serve`; use a reverse proxy. Full HTTP API schema and deployment details: [Running as a Service](/deployment-operations/running-as-service). diff --git a/src/content/docs/cli-reference/validate.md b/src/content/docs/cli-reference/validate.md index 9e4b124..d37c828 100644 --- a/src/content/docs/cli-reference/validate.md +++ b/src/content/docs/cli-reference/validate.md @@ -3,62 +3,62 @@ title: "sentrie validate" description: "Validate pack structure, syntax, and types without executing policies." --- -`validate` loads a policy pack and checks pack file, policy syntax, types, and references. It does not run policies. Use it in CI or before deployment to catch errors early. +When you want to catch policy and pack errors before running or deploying (e.g. in CI or before a release), you use `sentrie validate`. It loads the pack and checks structure, syntax, types, and references without executing any rules—so you get fast feedback and no side effects. -## Syntax +Here is the basic syntax: ```bash sentrie validate [ --pack-location ] [ --facts ] ``` -- **TARGET:** `namespace/policy` or `namespace/policy/rule`. Required. Identifies the policy (and optionally rule) to validate in context. -- **--pack-location:** Pack root directory. Default: `./`. -- **--facts:** Optional JSON object for type-checking facts against declarations. +**TARGET** is required (`namespace/policy` or `namespace/policy/rule`). **--pack-location** and **--facts** are optional; facts are used for type-checking when provided. -## Options +## Configuration & Arguments -| Name | Type | Required | Description | -| :--- | :--- | :--- | :--- | +You can point validation at a specific policy and pack using the following options: + +| Argument | Type | Required | What it does | +| :------- | :--- | :------- | :----------- | | TARGET | string | Yes | `namespace/policy` or `namespace/policy/rule`. Namespace and policy must exist; rule is optional (used for context). | | `--pack-location` | path | No | Directory containing the policy pack. Default: `./`. | | `--facts` | JSON string | No | Facts for type checking. Validates that fact types and required/optional match declarations. Default: `{}`. | **Returns:** Exit 0 if validation succeeds; non-zero with error messages if pack loading, parsing, type checking, or executor creation fails. No decision output; validation only. -## Examples +--- + +## Examples in Action -### Basic Usage +### Validating a policy in the current directory -Validate a policy in the current directory: +You have just edited a policy and want to confirm it loads and type-checks before committing. ```bash sentrie validate com/example/user_management/user_access ``` -Validate from a specific pack path: +### Validating from a specific pack path + +Your pack lives in a different directory (e.g. a monorepo subfolder). ```bash sentrie validate com/example/auth/access_control --pack-location ./policy-pack ``` -### Advanced Usage +### Type-checking facts against policy declarations -Validate with facts for type checking: +You want to ensure that the fact shapes and required/optional flags match the JSON you plan to send at runtime. ```bash sentrie validate com/example/user_management/user_access \ --facts '{"user":{"role":"admin","status":"active"}}' ``` -## Behavior & Constraints +--- -- **Checks performed:** Pack file structure (`sentrie.pack.toml`), policy file parsing and syntax, namespace/policy/rule references, type annotations and shape constraints, and executor creation (including TypeScript module loading). Does not execute rules. -- **Facts:** When `--facts` is provided, validates that fact types and required/optional match policy declarations and that values satisfy shapes. -- **Output:** Errors printed to stderr; no table or JSON decision output. +## Good to Know -## Constraints & Edge Cases +Before you rely on this in CI, keep a few boundaries in mind: -- Invalid or missing TARGET (unknown namespace/policy) → error. -- Pack load failure, parse error, type error, or reference error → non-zero exit; fix pack and re-run. -- `--facts` must be valid JSON; invalid JSON → error. -- Differs from `exec`: validate does not run policies or produce decision output; use `exec` for execution and results. See [sentrie exec](/cli-reference/exec) for execution. +- **Constraint:** Validation checks pack file structure, policy parsing and syntax, namespace/policy/rule references, type annotations and shape constraints, and executor creation (including TypeScript module loading). It does not execute rules. When `--facts` is provided, it validates that fact types and required/optional match declarations and that values satisfy shapes. Errors are printed to stderr; no table or JSON decision output. +- **Edge case:** Invalid or missing TARGET (unknown namespace/policy) → error. Pack load failure, parse error, type error, or reference error → non-zero exit. `--facts` must be valid JSON; invalid JSON → error. Unlike `exec`, validate does not run policies or produce decision output; use [sentrie exec](/cli-reference/exec) for execution. diff --git a/src/content/docs/deployment-operations/running-as-service.md b/src/content/docs/deployment-operations/running-as-service.md index 05b47a0..7ac55c8 100644 --- a/src/content/docs/deployment-operations/running-as-service.md +++ b/src/content/docs/deployment-operations/running-as-service.md @@ -82,9 +82,9 @@ Evaluates a policy or rule. `{target...}` = path segments: `namespace/policy` or **Returns:** See schemas above. 400 for invalid JSON or path; 404 for unknown policy/rule; 405 for wrong method; 500 for evaluation error. -## Examples +## Examples in Action -### Basic Usage +### Calling the decision endpoint with curl ```bash curl -X POST "http://localhost:7529/decision/com/example/auth/user/allow" \ @@ -92,7 +92,7 @@ curl -X POST "http://localhost:7529/decision/com/example/auth/user/allow" \ -d '{"facts":{"user":{"role":"admin"}}}' ``` -### Advanced Usage +### Passing multiple facts in the request body ```bash curl -X POST "http://localhost:7529/decision/com/example/auth/user" \ @@ -121,10 +121,11 @@ curl -X POST "http://localhost:7529/decision/com/example/auth/user" \ | 405 | Method not allowed (e.g. GET on /decision). | | 500 | Internal error during evaluation. | -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - Base URL: default `http://localhost:7529`. `--listen all` binds 0.0.0.0. Only POST is allowed for `/decision/{target...}`. Query parameters are parsed but not used for execution semantics. -## Constraints & Edge Cases - Facts must match policy declarations (required facts present; types/shapes valid). Missing or invalid facts can return 400 or 500. CORS headers are sent (`Access-Control-Allow-Origin: *` etc.). No built-in auth or rate limiting. diff --git a/src/content/docs/extensibility/writing-custom-typescript-modules.md b/src/content/docs/extensibility/writing-custom-typescript-modules.md index 2791291..f1b4648 100644 --- a/src/content/docs/extensibility/writing-custom-typescript-modules.md +++ b/src/content/docs/extensibility/writing-custom-typescript-modules.md @@ -15,7 +15,7 @@ use { fn1 } from "@local/path/to/module" [ as alias ] Built-in: `use { fn1 } from @sentrie/module` (no quotes). Local: quoted path or `@local/...`; resolved relative to current file or pack root. -## Parameters +## Configuration & Arguments | Element | Type | Required | Description | | :--- | :--- | :--- | :--- | @@ -25,9 +25,9 @@ Built-in: `use { fn1 } from @sentrie/module` (no quotes). Local: quoted path or **Returns:** N/A (import). Call as `alias.fn1(args)` in the policy. -## Examples +## Examples in Action -### Basic Usage +### Typical use ```text use { calculateAge, validateEmail } from "./utils.ts" as utils @@ -36,7 +36,7 @@ rule myrule = default false { } ``` -### Advanced Usage +### Going further ```text use { calculateAge, validateEmail } from "../helpers/validation.ts" @@ -46,12 +46,13 @@ yield utils.calculateAge(user.birthDate) >= 18 Path resolution: `./file.ts` = same directory as policy; `../parent.ts` = parent; `./utils/helper.ts` = subdirectory. All normalized to `@local` internally. `@local/user/id` → `$PACKROOT/user/id.ts`. -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - TypeScript files must live inside the policy pack root. They can import other local modules and built-in `@sentrie/*` modules. - Exported functions and types are available to policies. Relative imports in `.ts` files are resolved relative to that file and normalized to `@local`. - Default alias is the last path segment (e.g. `utils` for `./utils.ts`). Use `as alias` to override. -## Constraints & Edge Cases - Module must export the requested names. Invalid path or missing export causes load error. Use [Built-in TypeScript Modules](/reference/typescript_modules/) for common operations; add custom modules for pack-specific logic. diff --git a/src/content/docs/getting-started/enforcement.md b/src/content/docs/getting-started/enforcement.md index 18ec5c0..d2eef97 100644 --- a/src/content/docs/getting-started/enforcement.md +++ b/src/content/docs/getting-started/enforcement.md @@ -3,44 +3,50 @@ title: Enforcement description: "Sentrie returns decisions only; enforcement is implemented by the caller." --- -Sentrie is a deterministic policy engine. It evaluates facts against rules and returns decisions (e.g. boolean or trinary). It does not perform enforcement: no blocking, no API calls, no side effects. The caller uses the decision to enforce (allow/deny, redact, step-up auth, etc.). +Sentrie is a deterministic policy engine: it takes facts and rules and returns decisions (e.g. true, false, or unknown). It does not block requests, call APIs, or change state. When you need to actually allow/deny, redact, or step up auth, your system (IAM, API gateway, backend) reads the decision and enforces it. That split keeps the engine simple and safe to plug into any stack. -## Syntax +Here is the basic idea: -**CLI:** Decisions are printed to stdout; exit code indicates success or failure. Caller script or service interprets the output and enforces. +**CLI:** Decisions are printed to stdout; exit code indicates success or failure. Your script or service interprets the output and enforces. -**HTTP:** Caller sends a POST with facts; response body contains decision(s). Caller enforces based on the response. +**HTTP:** You POST facts to an endpoint; the response body contains the decision(s). Your service enforces based on that. ```text POST /decision/{namespace}/{policy}/{rule} Content-Type: application/json - Request body: { "facts": { "factName": value, ... } } - -Response: decision value(s); format depends on endpoint. +Response: decision value(s); see Running as a Service for schema. ``` -## Concepts +## Configuration & Arguments + +These concepts clarify how decisions and enforcement relate: + +| Concept | What it means | +| :------ | :------------ | +| Decision | The output of a rule: typically `true`, `false`, or `unknown`. Sentrie only returns this; it does not act on it. | +| Enforcement | What your system does with the decision: allow/deny, redact, step-up auth, throttle, etc. Implemented in IAM, gateway, backend, or feature flags. | +| Determinism | Same facts and policy → same decision. No mutable state or side effects inside the engine; safe to replay and audit. | +| Portability | Same policy runs the same via CLI or HTTP; enforcement logic lives in your integrating system. | -| Concept | Description | -| :--- | :--- | -| Decision | Output of a rule: typically `true`, `false`, or `unknown` (trinary). Sentrie only returns this; it does not act on it. | -| Enforcement | Action taken by the caller: allow/deny request, redact data, trigger step-up auth, throttle, etc. Implemented in IAM, API gateway, backend, or feature flag system. | -| Determinism | Same facts and policy → same decision. No mutable state or side effects inside the engine. Safe to replay and audit. | -| Portability | Same policy runs identically via CLI or HTTP; enforcement logic lives in the integrating system. | +**Returns:** Sentrie returns decision value(s). Exit code (CLI) or HTTP status indicates success or failure. Sentrie does not perform enforcement. -**Returns:** Sentrie returns decision value(s). Exit code (CLI) or HTTP status indicates success/failure. No enforcement is performed by Sentrie. +--- + +## Examples in Action -## Examples +### Letting the caller enforce from the CLI -### CLI: caller enforces from exit code and stdout +You run `sentrie exec` in a script; the script parses stdout and exit code and then allows or denies the operation. ```bash result=$(sentrie exec com/example/auth/access/allow --facts '{"user": {"role": "user"}}') # Parse $result; if allow is true, proceed; else deny or redirect. ``` -### HTTP: caller enforces from response body +### Letting the caller enforce from the HTTP API + +Your backend or gateway calls the Sentrie HTTP endpoint and then enforces based on the response body. ```bash curl -s -X POST "https://sentrie.host:7529/decision/com/example/auth/access/allow" \ @@ -48,24 +54,20 @@ curl -s -X POST "https://sentrie.host:7529/decision/com/example/auth/access/allo -d '{"facts": {"user": {"role": "admin", "status": "active"}}}' ``` -Caller reads the response; if decision is allow, the IAM/gateway/backend allows the request; otherwise it denies, redacts, or escalates. +The caller reads the response; if the decision is allow, it allows the request; otherwise it denies, redacts, or escalates. ### Typical enforcement roles -- **IAM / Auth:** Allow, deny, or trigger step-up based on decision. -- **API gateway:** Allow, throttle, or block route based on decision. -- **Backend:** Serve full response, redact fields, or return 403 based on decision. -- **Feature / entitlement:** Enable, disable, or cap usage based on decision. +- **IAM / Auth:** Allow, deny, or trigger step-up based on the decision. +- **API gateway:** Allow, throttle, or block the route based on the decision. +- **Backend:** Serve full response, redact fields, or return 403 based on the decision. +- **Feature / entitlement:** Enable, disable, or cap usage based on the decision. -## Behavior & Constraints +--- -- **No side effects:** Sentrie does not mutate state, call external APIs, or perform enforcement. It only evaluates and returns decisions. -- **Caller responsibility:** The system that calls Sentrie (CLI script, API gateway, backend service) must interpret the decision and enforce (allow/deny, redact, etc.). -- **Determinism:** Same inputs and policy produce the same decision. Safe for critical paths and replay. -- **Same policy, many enforcers:** One policy can be used by IAM, gateway, and backend; each enforces in its own way. +## Good to Know -## Constraints & Edge Cases +Before you implement this, keep a few boundaries in mind: -- Sentrie does not enforce. Missing or incorrect enforcement is a caller bug, not an engine behavior. -- Timeouts, network errors, and auth to the Sentrie endpoint are the caller’s responsibility. -- For HTTP, see Deployment & Operations (e.g. Running as a Service) for exact request/response schema and status codes. +- **Constraint:** Sentrie does not mutate state, call external APIs, or perform enforcement. It only evaluates and returns decisions. The system that calls Sentrie (CLI script, API gateway, backend) must interpret the decision and enforce. Same inputs and policy always produce the same decision; safe for critical paths and replay. +- **Edge case:** Sentrie does not enforce. Missing or incorrect enforcement is a caller bug. Timeouts, network errors, and auth to the Sentrie endpoint are the caller’s responsibility. For HTTP request/response schema and status codes, see [Running as a Service](/deployment-operations/running-as-service). diff --git a/src/content/docs/getting-started/installation.md b/src/content/docs/getting-started/installation.md index 23141a0..6c6c1b4 100644 --- a/src/content/docs/getting-started/installation.md +++ b/src/content/docs/getting-started/installation.md @@ -3,64 +3,88 @@ title: Installation description: Learn how to install Sentrie --- -Sentrie is a single binary executable. It has no external dependencies. +When you need to run Sentrie locally or on a server, you install a single binary. There are no external dependencies—just the executable. Convenience scripts are provided for macOS, Linux, and Windows so you can install or pin a version with one command. -On macOS, both M1 (arm64) and Intel (x64) executables are provided. On Windows and Linux, both x64 and arm64 executables are provided. +Here is the basic syntax: -Convenience scripts are provided for macOS, Linux, and Windows. - -## Installing the latest version - -### On macOS - -```bash -curl -fsSL https://sentrie.sh/install.sh | bash -``` - -### On Linux and WSL2 +**macOS / Linux / WSL2:** ```bash curl -fsSL https://sentrie.sh/install.sh | bash ``` -### On Windows +**Windows:** ```bash irm https://sentrie.sh/install.ps1 | iex ``` -### Verify Installation +**Verify:** ```bash sentrie --version ``` -This should display the current version of Sentrie. +## Configuration & Arguments -## Installing a specific version +You can install the latest or a specific version depending on your platform: -### On macOS +| Platform | Command (latest) | Command (specific version) | +| :------- | :--------------- | :-------------------------- | +| macOS, Linux, WSL2 | `curl -fsSL https://sentrie.sh/install.sh \| bash` | `curl -fsSL https://sentrie.sh/install.sh \| bash -s v0.1.0` | +| Windows | `irm https://sentrie.sh/install.ps1 \| iex` | `$v="0.1.0"; irm https://sentrie.sh/install.ps1 \| iex` | -For specific versions, use the install script: +**Returns:** N/A. The script installs the binary; `sentrie --version` confirms the install. + +--- + +## Examples in Action + +### Installing the latest version on macOS or Linux + +You want the current release and are on macOS, Linux, or WSL2. ```bash -curl -fsSL https://sentrie.sh/install.sh | bash -s v0.1.0 +curl -fsSL https://sentrie.sh/install.sh | bash ``` -### On Linux and WSL2 +### Installing the latest version on Windows + +You are on Windows and want to use the PowerShell installer. + +```bash +irm https://sentrie.sh/install.ps1 | iex +``` + +### Pinning a specific version + +You need a fixed version (e.g. for CI or reproducibility). + +**Unix:** ```bash curl -fsSL https://sentrie.sh/install.sh | bash -s v0.1.0 ``` -### On Windows +**Windows:** ```bash $v="0.1.0"; irm https://sentrie.sh/install.ps1 | iex ``` -### Verify Installation +### Verifying the installation + +You want to confirm the binary is on your PATH and see the version. ```bash sentrie --version ``` + +--- + +## Good to Know + +Before you rely on this in automation, keep a few boundaries in mind: + +- **Constraint:** Sentrie is a single binary; no extra runtime. Supported: macOS (arm64, x64), Linux (x64, arm64), Windows (x64, arm64). +- **Edge case:** Scripts install to a standard location and update PATH as appropriate for the platform. For custom installs or air-gapped environments, you can download the binary directly from the release artifacts. diff --git a/src/content/docs/getting-started/introduction.md b/src/content/docs/getting-started/introduction.md index c81378d..7957012 100644 --- a/src/content/docs/getting-started/introduction.md +++ b/src/content/docs/getting-started/introduction.md @@ -40,14 +40,15 @@ policy { - One namespace per file; namespace must be the first statement. - Policies contain facts (inputs), rules (logic), and exports (rules exposed for evaluation). -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - **Facts**: Required by default; use `?` for optional. All facts are non-nullable. Optional facts may have a default. - **Rules**: Must yield a value in the body when the `when` condition is true; otherwise the default (or `unknown` if no default) is used. - **Exports**: A policy must export at least one rule for it to be evaluable via CLI or API. - **TypeScript modules**: Allowed for extra functions; execution remains bounded by the engine. -## Constraints & Edge Cases - Policy logic cannot cause infinite loops or unbounded recursion in the Sentrie language. - Required facts must be provided at evaluation time or evaluation fails. diff --git a/src/content/docs/getting-started/quick-start.md b/src/content/docs/getting-started/quick-start.md index 5e03762..27cd451 100644 --- a/src/content/docs/getting-started/quick-start.md +++ b/src/content/docs/getting-started/quick-start.md @@ -3,9 +3,9 @@ title: Quick Start description: Install Sentrie and run your first policy evaluation. --- -Install the Sentrie binary and evaluate a policy from the command line. This page covers installation and a minimal run (no policy pack layout). +When you need to run policy checks from the command line or script a one-off evaluation, you install the Sentrie binary and use `sentrie exec` with a target and facts. This page gets you from zero to a first run. -## Syntax +Here is the basic syntax: Install (macOS, Linux, WSL2): @@ -19,60 +19,34 @@ Install (Windows): irm https://sentrie.sh/install.ps1 | iex ``` -Specific version (script): append version to the install script (e.g. `bash -s v0.1.0` for Unix; set `$v="0.1.0"` for Windows). - -Verify: +Verify and run a policy: ```bash sentrie --version +sentrie exec [or namespace/policy/rule] [ --facts '' ] ``` -## Writing your first policy - -```bash -sentrie init my-pack -``` +For a specific version, append it to the install script (e.g. `bash -s v0.1.0` on Unix; set `$v="0.1.0"` on Windows). -This will create a new policy pack in the current directory. +## Configuration & Arguments -```bash -$ echo 'namespace auth { \ - policy user { \ - rule allow = default false { \ - yield true \ - } \ - } \ -}' > my-pack/policy.sentrie -``` - -```bash -sentrie exec auth/user/allow +You control what gets evaluated using the pack directory, target, and facts: -Namespace: auth -Policy: user - -Rules: - ✓ allow: ✓ True - -Values: - ✓ allow: true -``` - -## Parameters - -| Step | Required | Description | -| :--------------- | :------- | :----------------------------------------------------------------------------------- | -| Policy pack path | No | Directory containing `*.sentrie.toml` pack manifest. Default: current directory. | -| Target | Yes | `namespace/policy` or `namespace/policy/rule`. | -| Facts | Depends | JSON object of fact names to values; required if the policy declares required facts. | +| Argument | Required | What it does | +| :------- | :------- | :------------ | +| Policy pack path | No | Directory containing your `*.sentrie` files (and optional pack manifest). Default: current directory. | +| Target | Yes | `namespace/policy` runs all exported rules; `namespace/policy/rule` runs a single rule. | +| Facts | Depends | JSON object of fact names to values. Required if the policy declares required facts. | **Returns:** Exit code 0 on success; non-zero on evaluation or CLI error. Decision output is printed to stdout. -## Examples +--- + +## Examples in Action -### Basic Usage +### Running a single rule with inline facts -Create a minimal pack and run one rule: +You have a policy that expects a `user` fact and exports a rule `allow`. You want to see the decision for one concrete user. ```bash mkdir my-pack && cd my-pack @@ -100,15 +74,15 @@ policy access { } ``` -Evaluate (from the pack directory): +From the pack directory, run the rule with facts: ```bash sentrie exec com/example/app/access/allow --facts '{"user": {"role": "user", "status": "active"}}' ``` -### Advanced Usage +### Evaluating every exported rule in a policy -Evaluate all exported rules in a policy and pass multiple facts: +You want to see the outcome of all exported rules in one policy (e.g. for debugging or auditing). Omit the rule name from the target. ```bash sentrie exec com/example/app/access --facts '{"user": {"role": "admin", "status": "active"}}' @@ -116,16 +90,13 @@ sentrie exec com/example/app/access --facts '{"user": {"role": "admin", "status" Facts JSON keys must match the fact names (or aliases) declared in the policy. Required facts must be present. -## Behavior & Constraints +--- -- **Single binary**: No extra runtime; the executable is self-contained. -- **Platforms**: macOS (arm64, x64), Linux (x64, arm64), Windows (x64, arm64). -- **Facts**: Required facts must be provided; otherwise evaluation fails. Optional facts may be omitted if they have defaults. -- **Target**: Use `namespace/policy` to evaluate all exported rules; use `namespace/policy/rule` for a single rule. +## Good to Know -## Constraints & Edge Cases +Before you rely on this in scripts or CI, keep a few boundaries in mind: -- Missing required fact → evaluation error. -- Invalid JSON in `--facts` → CLI error. -- Invalid target path (unknown namespace/policy/rule) → error. -- At least one rule must be exported from a policy to be executable via `sentrie exec`. +- **Single binary:** No extra runtime; the executable is self-contained. Supported platforms: macOS (arm64, x64), Linux (x64, arm64), Windows (x64, arm64). +- **Facts:** Required facts must be provided; otherwise evaluation fails. Optional facts may be omitted if they have defaults. +- **Target:** Use `namespace/policy` to evaluate all exported rules; use `namespace/policy/rule` for a single rule. Invalid or missing target (unknown namespace/policy/rule) causes an error. +- **Edge case:** Missing required fact → evaluation error. Invalid JSON in `--facts` → CLI error. At least one rule must be exported from a policy to be executable via `sentrie exec`. diff --git a/src/content/docs/getting-started/running-your-policy.md b/src/content/docs/getting-started/running-your-policy.md index 0a16e0f..bcfe1ad 100644 --- a/src/content/docs/getting-started/running-your-policy.md +++ b/src/content/docs/getting-started/running-your-policy.md @@ -3,32 +3,35 @@ title: Running your first Policy description: "Evaluate policies with sentrie exec: target, facts JSON, and output format." --- -Use `sentrie exec` to evaluate one or all exported rules in a policy. You supply a target (`namespace/policy` or `namespace/policy/rule`) and a JSON object of facts. The CLI returns rule results and exit code. +When you want to run a policy from the command line (e.g. to test a rule or script a one-off check), you use `sentrie exec`. You give it a target (which policy and optionally which rule) and a JSON object of facts; the CLI prints the decision output and exit code. -## Syntax +Here is the basic syntax: ```bash sentrie exec TARGET [ --facts JSON ] [ --pack-location PATH ] ``` -- **TARGET:** `namespace/policy` (all exported rules) or `namespace/policy/rule` (single rule). Run from pack directory or use `--pack-location PATH`. -- **Facts:** JSON object. Keys are fact names (or aliases). Required facts must be present; optional facts may be omitted if they have defaults. +**TARGET** is `namespace/policy` (all exported rules) or `namespace/policy/rule` (single rule). Run from the pack directory or pass `--pack-location`. **Facts** are a JSON object whose keys match fact names (or aliases); required facts must be present. -## Options +## Configuration & Arguments -| Concept | Required | Description | -| :--- | :--- | :--- | +You can control what gets run and where the pack lives using these options: + +| Argument | Required | What it does | +| :------- | :------- | :----------- | | Target | Yes | `namespace/policy` or `namespace/policy/rule`. Slashes match namespace and policy/rule identifiers. | | Facts | Depends | JSON object. Required if the policy declares required facts. Keys match fact names (or aliases). | | Pack path | No | Default: current directory. Use `--pack-location PATH` to point at a different pack root. | -**Returns:** Exit code 0 on success; non-zero on evaluation or CLI error. Rule names and decision values are printed to stdout. Output includes namespace, policy, rules (match and value), and optional attachments. +**Returns:** Exit code 0 on success; non-zero on evaluation or CLI error. Rule names and decision values are printed to stdout (namespace, policy, rules, values, optional attachments). + +--- -## Examples +## Examples in Action -### Evaluate a single rule +### Evaluating a single rule with inline facts -Policy has namespace `com/example/user_management`, policy `user_access`, exported rule `allow_user`. Required fact: `user` (shape `User` with `role`, `status`). +You have a policy that exports `allow_user` and requires a `user` fact. You want to see the decision for one concrete user. ```bash sentrie exec com/example/user_management/user_access/allow_user --facts '{"user": {"role": "user", "status": "active"}}' @@ -47,9 +50,9 @@ Values: ✓ allow_user: true ``` -### Evaluate all exported rules in a policy +### Evaluating every exported rule in a policy -Omit the rule name to run every exported rule: +You want to run all exported rules in one go (e.g. for debugging or auditing). Omit the rule name from the target. ```bash sentrie exec com/example/user_management/user_access --facts '{"user": {"role": "admin", "status": "active"}}' @@ -72,21 +75,17 @@ Values: ### Using a specific pack directory +Your policy pack lives outside the current directory; you want to point `exec` at it explicitly. + ```bash sentrie exec com/example/user_management/user_access --pack-location /path/to/my-pack --facts '{"user": {"role": "user", "status": "active"}}' ``` -## Behavior & Constraints +--- -- **Target format:** `namespace/policy` or `namespace/policy/rule`. Namespace and policy/rule must exist; rule must be exported. -- **Facts JSON:** Keys must match fact names (or aliases) in the policy. Types must satisfy the declared shapes. Required facts missing → evaluation error. -- **Working directory:** If `--pack-location` is omitted, the current directory is used as the pack root (must contain `*.sentrie` and optionally `sentrie.pack.toml`). -- **Exit code:** 0 on success; non-zero on parse error, missing fact, or evaluation failure. +## Good to Know -## Constraints & Edge Cases +Before you rely on this in scripts or CI, keep a few boundaries in mind: -- Missing required fact → evaluation error; optional fact may be omitted if it has a default. -- Invalid or malformed JSON in `--facts` → CLI error. -- Invalid target (unknown namespace, policy, or rule) → error. -- Rule name in target must be an exported rule; otherwise target is invalid. -- At least one rule must be exported from the policy to be executable via `sentrie exec`. +- **Constraint:** Target must be `namespace/policy` or `namespace/policy/rule`; namespace and policy/rule must exist and the rule must be exported. Facts JSON keys must match fact names (or aliases); types must satisfy the declared shapes. If `--pack-location` is omitted, the current directory is the pack root (must contain `*.sentrie` and optionally `sentrie.pack.toml`). +- **Edge case:** Missing required fact → evaluation error; optional fact may be omitted if it has a default. Invalid or malformed JSON in `--facts` → CLI error. Invalid target (unknown namespace, policy, or rule) → error. At least one rule must be exported from the policy to be executable via `sentrie exec`. diff --git a/src/content/docs/getting-started/writing-your-first-policy.md b/src/content/docs/getting-started/writing-your-first-policy.md index 4233679..5691cbd 100644 --- a/src/content/docs/getting-started/writing-your-first-policy.md +++ b/src/content/docs/getting-started/writing-your-first-policy.md @@ -3,9 +3,9 @@ title: Writing your first Policy description: "Minimal policy structure: namespace, policy, shape, facts, rules, exports." --- -A Sentrie policy file contains one namespace and one or more policies. Each policy declares facts (inputs), rules (decision logic), and exports (rules exposed for evaluation). This page gives the minimal structure and a complete example. +When you need to define who can do what (or any decision your app will enforce), you describe it in a Sentrie policy: one namespace per file, shapes for your input data, and policies that declare facts, rules, and exports. This page walks through the minimal structure and a few typical scenarios. -## Syntax +Here is the basic syntax: ```text namespace SLUG @@ -20,23 +20,30 @@ policy IDENT { } ``` -- File must start with exactly one `namespace`. One or more `shape` and `policy` blocks follow. -- Policy must have at least one `rule` and at least one `export decision of`. +The file must start with exactly one `namespace`. One or more `shape` and `policy` blocks follow. Each policy must have at least one `rule` and at least one `export decision of`. -## Concepts +## Configuration & Arguments -| Concept | Required | Description | -| :-------- | :------- | :------------------------------------------------------------------------------------------------------------------ | -| Namespace | Yes | Single `namespace SLUG` per file; first statement. Slash-separated (e.g. `com/example/app`). | -| Shape | No | Data model for facts. Required/optional fields: `field!`, `field?`. | -| Policy | No | Named block containing facts, rules, and exports. | -| Fact | No | Input to the policy. Required by default; `?` makes optional. Only optional facts may have `default`. Non-nullable. | -| Rule | Yes (≥1) | Block that yields a decision. May reference other rules in the same policy. | -| Export | Yes (≥1) | `export decision of ruleName` makes the rule callable via CLI/API and importable. | +You can structure a policy using these elements: -## Examples +| Argument | Required | What it does | +| :------- | :------- | :----------- | +| Namespace | Yes | Single `namespace SLUG` per file; first statement. Slash-separated (e.g. `com/example/app`). | +| Shape | No | Data model for facts. Required fields: `field!`; optional: `field?`. | +| Policy | No | Named block containing facts, rules, and exports. | +| Fact | No | Input to the policy. Required by default; `?` makes optional. Only optional facts may have `default`. Non-nullable. | +| Rule | Yes (≥1) | Block that yields a decision. May reference other rules in the same policy. | +| Export | Yes (≥1) | `export decision of ruleName` makes the rule callable via CLI/API and importable. | -### Minimal policy (one rule) +**Returns:** N/A (authoring). At runtime, evaluation returns the exported rule decision(s) (e.g. boolean). + +--- + +## Examples in Action + +### Defining a minimal policy with one rule + +You want a single allow/deny decision based on a user shape (e.g. role). You need one namespace, one shape, one rule, and one export. ```sentrie namespace com/example/user_management @@ -59,6 +66,8 @@ policy user_access { ### Composing rules and exporting multiple rules +You want to reuse one rule inside another (e.g. “admin or active user”) and expose both outcomes so the CLI or API can call either. + ```sentrie namespace com/example/user_management @@ -83,7 +92,9 @@ policy user_access { } ``` -### Optional fact with default +### Using an optional fact with a default + +You have a fact that is optional (e.g. context) and you want a default when the caller doesn’t supply it. ```sentrie policy user_access { @@ -97,17 +108,12 @@ policy user_access { } ``` -## Behavior & Constraints +--- -- **File structure:** One namespace per file; namespace must be the first statement. Shapes and policies follow. -- **Facts:** Declared before rules. Required facts must be provided at evaluation time; optional facts may be omitted or have defaults. -- **Rules:** May reference other rules in the same policy by name (e.g. `yield allow_admin or ...`). At least one rule must be exported for the policy to be executable. -- **Exports:** Only exported rules are available to `sentrie exec` and the HTTP API. A policy must export at least one rule. +## Good to Know -## Constraints & Edge Cases +Before you implement this, keep a few boundaries in mind: -- Namespace slug uses slashes (e.g. `com/example/app`). No leading/trailing slash. -- Shape field required: `field!: type`. Optional: `field?: type`. -- Fact without `?` is required; missing required fact at evaluation → error. -- Optional fact (`?`) may have `default expr`; if omitted at evaluation, default is used. -- Rule body must yield a value (e.g. `yield expr`). Default when omitted: `default false` or `default true` as declared. +- **Constraint:** One namespace per file; namespace must be the first statement. Facts are declared before rules. Required facts must be provided at evaluation time; optional facts may be omitted or use defaults. At least one rule must be exported for the policy to be executable. +- **Constraint:** Only exported rules are available to `sentrie exec` and the HTTP API. Rules in the same policy may reference each other by name (e.g. `yield allow_admin or ...`). +- **Edge case:** Namespace slug uses slashes (e.g. `com/example/app`); no leading or trailing slash. Shape: `field!: type` for required, `field?: type` for optional. Fact without `?` is required; missing required fact at evaluation → error. Optional fact (`?`) may have `default expr`. Rule body must yield a value; you can declare `default false` or `default true`. diff --git a/src/content/docs/language-concepts/pattern-matching-conditionals.md b/src/content/docs/language-concepts/pattern-matching-conditionals.md index 350fb00..db379d3 100644 --- a/src/content/docs/language-concepts/pattern-matching-conditionals.md +++ b/src/content/docs/language-concepts/pattern-matching-conditionals.md @@ -3,7 +3,6 @@ title: Pattern Matching & Conditionals description: How conditional selection (ternary, Elvis), pattern matching (matches), and state checks work in Sentrie. --- - Sentrie uses trinary logic (`true`, `false`, `unknown`) and provides the ternary and Elvis operators for conditional values, plus `matches` for regex and `is defined` / `is empty` for state checks. This page describes how these work. ## Syntax @@ -18,21 +17,21 @@ Sentrie uses trinary logic (`true`, `false`, `unknown`) and provides the ternary **Emptiness:** `value is empty` | `value is not empty` -## Reference +## Configuration & Arguments -| Construct | Left | Right | Description | -| :----------- | :------------------ | :-------------------- | :---------------------------------------------------------------- | -| `? :` | condition (trinary) | trueValue, falseValue | If condition is truthy, result is trueValue; else falseValue. | -| `?:` | expression | defaultValue | If expression is truthy, result is expression; else defaultValue. | -| `matches` | string | string (regex) | True if string matches the regex pattern. | -| `is defined` | any | - | True if value is not undefined. | -| `is empty` | string/list/map | - | True if empty (e.g. `""`, `[]`, `{}`). | +| Construct | Left | Right | Description | +| :----------------- | :------------------ | :-------------------- | :---------------------------------------------------------------- | +| `? :` | condition (trinary) | trueValue, falseValue | If condition is truthy, result is trueValue; else falseValue. | +| `?:` | expression | defaultValue | If expression is truthy, result is expression; else defaultValue. | +| `matches` | string | string (regex) | True if string matches the regex pattern. | +| `is [not] defined` | any | - | True if value is [not] undefined. | +| `is [not] empty` | string/list/map | - | True if [not] empty (e.g. `""`, `[]`, `{}`). | **Returns:** For ternary/Elvis: the selected value (any type). For `matches`, `is defined`, `is empty`: boolean (or trinary where applicable). Non-truthy for Elvis includes `false`, `null`, `undefined` (treated as unknown), `0`, `""`, empty collections. -## Examples +## Examples in Action -### Basic Usage +### Typical use ```sentrie let status: string = age >= 18 ? "adult" : "minor" @@ -41,7 +40,7 @@ let valid: bool = email matches "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2, let hasPhone: bool = user.phone is defined ``` -### Advanced Usage +### Going further ```sentrie let final_price: number = product.in_stock @@ -51,7 +50,9 @@ let final_price: number = product.in_stock let safeItems: list[string] = items ?: [] ``` -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - **Ternary:** Condition is evaluated first; only the chosen branch is evaluated. Nested ternaries are allowed; precedence is right-associative. - **Elvis:** Shorthand for “use this value or default.” `null`/`undefined` are non-truthy (unknown in trinary), so the default is used. @@ -59,8 +60,6 @@ let safeItems: list[string] = items ?: [] - **is defined:** Use for optional shape fields or values that may be undefined. Accessing undefined without checking can propagate unknown. - **is empty:** Applies to strings, lists, and maps. Non-empty means at least one character or element. -## Constraints & Edge Cases - - Unknown (trinary) in a condition is not truthy; the false branch of ternary is used, and Elvis returns the default. - Regex syntax is that of the engine (e.g. escaping in strings). Invalid regex causes error. - `in` / `contains` for collections are documented in the reference; use with conditionals as needed. diff --git a/src/content/docs/language-concepts/policy-composition.md b/src/content/docs/language-concepts/policy-composition.md index a06f3a9..8185483 100644 --- a/src/content/docs/language-concepts/policy-composition.md +++ b/src/content/docs/language-concepts/policy-composition.md @@ -24,7 +24,7 @@ rule localName = import decision of ruleName with targetFact2 as expression2 ``` -## Reference +## Configuration & Arguments | Element | Type | Required | Description | | :--- | :--- | :--- | :--- | @@ -35,9 +35,9 @@ rule localName = import decision of ruleName **Returns:** Exported rule’s decision (trinary or value). If attachments exist, import returns an object with the decision and attachment fields (e.g. `authResult.role`). -## Examples +## Examples in Action -### Basic Usage +### Typical use ```sentrie namespace com/example/auth @@ -72,7 +72,7 @@ policy documentAccess { } ``` -### Advanced Usage +### Going further Export with attachments; import and use them: @@ -106,7 +106,9 @@ policy resources { } ``` -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - **Export:** A policy must export at least one rule. Only exported rules are executable via CLI/API and importable. Rules in the same policy can reference each other without export. - **Import:** The imported rule runs in a sandbox: it sees only the facts provided by `with` clauses. It cannot access the calling policy’s other facts or context. Facts are type-checked against the target policy. @@ -114,7 +116,6 @@ policy resources { - **Recursion:** Circular imports (A imports B, B imports A) are prevented and will fail. - **Namespace:** Use full `namespace/policy` or, in the same namespace, policy name only. Fact names in `with` must match the target policy’s fact alias. -## Constraints & Edge Cases - Rule not found: ensure the rule is exported and the namespace/policy path and rule name are correct (case-sensitive). - Fact not found: use the target policy’s fact alias in `with`, not the original name. diff --git a/src/content/docs/language-concepts/type-system-shapes.md b/src/content/docs/language-concepts/type-system-shapes.md index 341d961..7f7a85b 100644 --- a/src/content/docs/language-concepts/type-system-shapes.md +++ b/src/content/docs/language-concepts/type-system-shapes.md @@ -39,9 +39,9 @@ shape Name { **Returns:** N/A (type system). Constraint validation fails at runtime if a value does not meet the type or constraints; evaluation aborts. -## Examples +## Examples in Action -### Basic Usage +### Typical use ```sentrie shape User { @@ -53,7 +53,7 @@ shape User { let u: User = { name: "Alice", age: 28 } ``` -### Advanced Usage +### Going further ```sentrie shape Base { id!: string } @@ -62,7 +62,9 @@ shape Extended with Base { role!: string } let e: Extended = { id: "1", role: "admin" } ``` -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - **Primitives:** `number` (float64), `string`, `trinary` (`true`/`false`/`unknown`), `bool` (subset of trinary), `document` (JSON-like). - **Collections:** `list[T]` index by number; `map[T]` keys are strings (dot or `["key"]` access); `record[T1,T2,...]` fixed-length tuple. @@ -70,7 +72,6 @@ let e: Extended = { id: "1", role: "admin" } - **Constraints:** Validate at runtime. Failing constraint validation aborts evaluation immediately. - **Cast:** `cast expr as Type` converts and validates against the target type (and its constraints). -## Constraints & Edge Cases - Map keys must be strings. - Type annotation on `let` is optional; if omitted, values are not validated against types. diff --git a/src/content/docs/reference/arithmetic-operations.md b/src/content/docs/reference/arithmetic-operations.md index 822b8c1..f8620d5 100644 --- a/src/content/docs/reference/arithmetic-operations.md +++ b/src/content/docs/reference/arithmetic-operations.md @@ -18,7 +18,7 @@ expr % expr Unary: `+ expr` | `- expr` -## Reference +## Configuration & Arguments | Operator | Description | Result | | :--- | :--- | :--- | @@ -30,9 +30,9 @@ Unary: `+ expr` | `- expr` **Returns:** `number`. Division by zero aborts evaluation. -## Examples +## Examples in Action -### Basic Usage +### Typical use ```sentrie let sum: number = 5 + 3 @@ -42,18 +42,19 @@ let quot: number = 15 / 3 let rem: number = 10 % 3 ``` -### Advanced Usage +### Going further ```sentrie let area: number = rect.width * rect.height let safe: number = divisor != 0 ? 10 / divisor : 0.0 ``` -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - All operands are `number`; mixed integer/float is allowed. Division is float (e.g. 7/2 = 3.5). - Modulo: remainder after division; divisor zero aborts. -## Constraints & Edge Cases - Division by zero aborts evaluation. Use a guard (e.g. ternary) to avoid. diff --git a/src/content/docs/reference/boolean-operations.md b/src/content/docs/reference/boolean-operations.md index 55e392c..7b919d5 100644 --- a/src/content/docs/reference/boolean-operations.md +++ b/src/content/docs/reference/boolean-operations.md @@ -3,40 +3,46 @@ title: Boolean Operations description: Logical (and, or, xor, not), comparison (==, !=, <, <=, >, >=), pattern (matches), and conditional operators. --- -Logical, comparison, and pattern operators produce trinary or bool results. Operands are trinary for logical/comparison; [trinary](/reference/trinary) semantics apply. Pattern operator `matches` is string-only. +When you need to combine conditions, compare values, or branch on truthiness, you use Sentrie’s logical, comparison, pattern, and conditional operators. They keep a consistent story: logical and comparison ops use [trinary](/reference/trinary) semantics (true, false, unknown), and the pattern operator `matches` works on strings only. -## Syntax +Here is the basic syntax: -**Logical (binary):** `and` | `or` | `xor` +**Logical (binary):** `and` | `or` | `xor` -**Negation (unary):** `not expr` | `! expr` — one operand; result is trinary NOT. +**Negation (unary):** `not expr` | `! expr` -**Comparison:** `==` | `!=` | `is` | `is not` | `<` | `<=` | `>` | `>=` +**Comparison:** `==` | `!=` | `is` | `is not` | `<` | `<=` | `>` | `>=` -**Pattern:** `string matches pattern` (pattern is a regex string) +**Pattern:** `string matches pattern` (pattern is a regex string) **Conditional:** `condition ? trueValue : falseValue` | `expr ?: default` -## Reference +## Configuration & Arguments -| Operator | Description | Returns | -| :------------------- | :-------------------------------------------------------------------------- | :-------------------- | -| `and` | Logical AND (Kleene) | trinary | -| `or` | Logical OR (Kleene) | trinary | -| `xor` | Logical XOR | trinary | -| `not`, `!` | Logical NOT (unary). Single operand; converted to trinary then negated. | trinary | -| `==`, `is` | Equality | trinary | -| `!=`, `is not` | Inequality | trinary | -| `<`, `<=`, `>`, `>=` | Ordering | trinary | -| `matches` | String matches regex (left: string, right: pattern). Invalid regex → error. | bool | -| `? :` | Ternary | type of chosen branch | -| `?:` | Elvis (default if not truthy) | type | +You can combine and compare values using these operators: -**Returns:** Trinary or the selected value. Non-truthy for Elvis: `false`, `unknown`, `null`, `0`, `""`, `empty collection`. +| Operator | What it does | Returns | +| :------- | :----------- | :------ | +| `and` | Logical AND (Kleene) | trinary | +| `or` | Logical OR (Kleene) | trinary | +| `xor` | Logical XOR | trinary | +| `not`, `!` | Logical NOT (unary). Single operand; converted to trinary then negated. | trinary | +| `==`, `is` | Equality | trinary | +| `!=`, `is not` | Inequality | trinary | +| `<`, `<=`, `>`, `>=` | Ordering | trinary | +| `matches` | String matches regex (left: string, right: pattern). Invalid regex → error. | bool | +| `? :` | Ternary: pick trueValue or falseValue by condition | type of chosen branch | +| `?:` | Elvis: use expression if truthy, else default | type | -## Examples +**Returns:** Trinary or the selected value. For Elvis, non-truthy means `false`, `unknown`, `null`, `0`, `""`, or empty collection. -### Basic Usage +--- + +## Examples in Action + +### Combining conditions and using a default value + +You are building a rule that depends on role and age, and you want a readable label or a fallback when a field is missing. ```sentrie let a: bool = true and false @@ -46,7 +52,9 @@ let d: string = age >= 18 ? "adult" : "minor" let e: string = user.name ?: "Anonymous" ``` -### Advanced Usage +### Checking role with regex and negated membership + +You need to allow access for admins or active users, validate an email format, and exclude guests. ```sentrie let f: bool = user.role == "admin" or (user.role == "user" and user.status == "active") @@ -54,15 +62,13 @@ let g: bool = email matches `^[a-z]+@[a-z]+\\.com$` let h: bool = not (role in ["guest"]) and status == "active" ``` -## Behavior & Constraints +--- -- **not / !:** Unary prefix; one operand. Operand is converted to trinary ([trinary](/reference/trinary)) then negated (true↔false, unknown stays unknown). -- Short-circuit: `and`/`or` evaluate left-to-right; right side may be skipped. Ternary evaluates only the chosen branch. -- Comparison: both sides must be comparable; result is trinary. -- **matches:** Left and right must be strings. Right is interpreted as a [Go regexp](https://pkg.go.dev/regexp) pattern. Invalid pattern string causes an evaluation error. +## Good to Know -## Constraints & Edge Cases +Before you wire these into policies, keep a few boundaries in mind: -- **not / !:** `not unknown` yields `unknown`. Use `not expr` or `! expr`; no space required after `!`. -- `unknown` in logical ops propagates per Kleene. Equality with `unknown` yields `unknown`. -- **matches:** Non-string operands or invalid regex → error. For membership in collections or substrings, use [in](/reference/membership-operations) or [contains](/reference/membership-operations). +- **not / !:** Unary prefix; one operand. It is converted to trinary then negated (true↔false; unknown stays unknown). `not unknown` yields `unknown`. You can write `not expr` or `! expr` (no space required after `!`). +- **Short-circuit:** `and` and `or` evaluate left-to-right; the right side may be skipped. The ternary evaluates only the chosen branch. +- **Comparison:** Both sides must be comparable; result is trinary. Equality with `unknown` yields `unknown`. +- **matches:** Left and right must be strings. The right side is interpreted as a [Go regexp](https://pkg.go.dev/regexp) pattern. Invalid pattern string causes an evaluation error. For membership in collections or substrings, use [in](/reference/membership-operations) or [contains](/reference/membership-operations). diff --git a/src/content/docs/reference/collection-operations.md b/src/content/docs/reference/collection-operations.md index 331f83a..91e455a 100644 --- a/src/content/docs/reference/collection-operations.md +++ b/src/content/docs/reference/collection-operations.md @@ -20,7 +20,7 @@ distinct collection Index parameter is optional in some forms. For maps, element is key-value or value depending on operation. -## Reference +## Configuration & Arguments | Operation | Input | Output | Description | | :--- | :--- | :--- | :--- | @@ -34,9 +34,9 @@ Index parameter is optional in some forms. For maps, element is key-value or val **Returns:** As in table. Empty collection: `any` false, `all` true, `count` 0. Reduce with empty collection returns initial. -## Examples +## Examples in Action -### Basic Usage +### Typical use ```sentrie let has_even: bool = any numbers as num, idx { yield num % 2 == 0 } @@ -48,19 +48,20 @@ let n: number = count numbers let uniq: list[number] = distinct numbers ``` -### Advanced Usage +### Going further ```sentrie let sum: number = reduce scores from 0 as acc, score, idx { yield acc + score } let avg: number = sum / count scores ``` -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - Only valid on collections (lists, maps). Original collection is not modified. - Block must yield once per iteration. Type of yield must match (trinary for any/all/filter predicate; expr for map/reduce). -## Constraints & Edge Cases - Empty collection: `any` → false, `all` → true, `filter`/`map`/`distinct` → empty, `count` → 0, `reduce` → initial. - Reduce: first iteration uses initial as acc; subsequent use previous yield as acc. diff --git a/src/content/docs/reference/constraints.md b/src/content/docs/reference/constraints.md index d8ebd49..1479aa2 100644 --- a/src/content/docs/reference/constraints.md +++ b/src/content/docs/reference/constraints.md @@ -14,7 +14,7 @@ type @constraint1(args) @constraint2 Examples: `number @min(0) @max(100)`, `string @email`, `list[string @one_of("a","b")]`. -## Reference +## Configuration & Arguments | Category | Constraints (examples) | | :--- | :--- | @@ -24,26 +24,27 @@ Examples: `number @min(0) @max(100)`, `string @email`, `list[string @one_of("a", **Returns:** N/A. Constraint failure raises an error and aborts evaluation. -## Examples +## Examples in Action -### Basic Usage +### Typical use ```text let u: number @min(0) @max(100) = 50 shape Permission string @one_of("read", "write", "delete") ``` -### Advanced Usage +### Going further ```text let permissions: list[string @one_of("read", "write", "delete")] = ["read", "write"] ``` -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - Constraints are checked at runtime when a value is assigned or cast to the constrained type. - Order of application is defined by the runtime. All specified constraints must pass. -## Constraints & Edge Cases - Failing constraint validation aborts evaluation immediately. Use shapes to reuse constrained types. diff --git a/src/content/docs/reference/facts.md b/src/content/docs/reference/facts.md index 57c52b1..444762a 100644 --- a/src/content/docs/reference/facts.md +++ b/src/content/docs/reference/facts.md @@ -3,52 +3,56 @@ title: Facts description: "Fact declaration syntax: required/optional, type, alias, default." --- +When you need to pass structured input into a policy (e.g. user, request, or config), you declare facts. Facts are named, typed inputs declared at the top of the policy. They are required by default; add `?` for optional. Only optional facts may have a default. Facts are non-nullable so evaluation stays predictable. -Facts are named inputs to a policy. They are declared at the top of the policy (after comments and other facts). Required by default; use `?` for optional. Only optional facts may have a default. Facts are non-nullable. - -## Syntax +Here is the basic syntax: ```text fact name : type [ as alias ] [ default expr ] -- required fact name? : type [ as alias ] [ default expr ] -- optional ``` -## Reference +## Configuration & Arguments + +You can customize each fact using these parts: -| Part | Type | Required | Description | -| :------------- | :-------------- | :------- | :-------------------------------------------------- | -| `name` | identifier | Yes | Declaration name. | -| `name?` | - | No | `?` makes the fact optional. | -| `type` | shape/primitive | Yes | Type of the fact value. | -| `as alias` | identifier | No | Name used in the policy body; default is `name`. | -| `default expr` | expression | No | Only for optional facts; used when fact is omitted. | +| Argument | Type | Required | What it does | +| :------- | :--- | :------- | :----------- | +| `name` | identifier | Yes | Declaration name. | +| `name?` | - | No | `?` makes the fact optional. | +| `type` | shape/primitive | Yes | Type of the fact value. | +| `as alias` | identifier | No | Name used in the policy body; default is `name`. | +| `default expr` | expression | No | Only for optional facts; used when the fact is omitted. | **Returns:** N/A (declaration). At evaluation time, fact names (or aliases) are bound to the provided JSON input. -## Examples +--- + +## Examples in Action -### Basic Usage +### Declaring a required user fact and an optional context + +You need a user shape for every evaluation and an optional context that defaults when not supplied. ```sentrie fact user: User as currentUser fact context?: Context as ctx default {} ``` -### Advanced Usage +### Using primitive types and document with defaults + +You have a string fact with an alias and an optional document fact for config. ```sentrie fact userId: string as id fact config?: document as settings default { "env": "production" } ``` -## Behavior & Constraints +--- -- Facts must be declared before rules; only facts or comments may precede fact declarations. -- Required facts must be supplied at evaluation or evaluation fails. -- Optional facts may be omitted; if provided they must be non-null. Default is used when omitted. -- Null is not allowed for any fact. +## Good to Know -## Constraints & Edge Cases +Before you implement this, keep a few boundaries in mind: -- Fact name in `with` for imports must match the **alias** (or name if no `as`) in the target policy. -- Type of the supplied value must match the fact type (and constraints) or evaluation fails. +- **Constraint:** Facts must be declared before rules; only facts or comments may precede fact declarations. Required facts must be supplied at evaluation or evaluation fails. Optional facts may be omitted; if provided they must be non-null. Null is not allowed for any fact. Default is used when an optional fact is omitted. +- **Edge case:** Fact name in `with` for imports must match the **alias** (or name if no `as`) in the target policy. Type of the supplied value must match the fact type (and constraints) or evaluation fails. diff --git a/src/content/docs/reference/functions.md b/src/content/docs/reference/functions.md index 4926050..1a37fbd 100644 --- a/src/content/docs/reference/functions.md +++ b/src/content/docs/reference/functions.md @@ -15,7 +15,7 @@ alias.functionName(arg1, arg2, ...) Import: `use { fn1, fn2 } from source [ as alias ]` -## Reference +## Configuration & Arguments | Element | Type | Required | Description | | :--- | :--- | :--- | :--- | @@ -25,9 +25,9 @@ Import: `use { fn1, fn2 } from source [ as alias ]` **Returns:** Per function; see module docs. Invalid args or runtime errors abort evaluation. -## Examples +## Examples in Action -### Basic Usage +### Typical use ```sentrie use { sha256 } from @sentrie/hash @@ -36,18 +36,19 @@ let h = sha256(data) let t = time.now() ``` -### Advanced Usage +### Going further ```sentrie use { calculateAge, validateEmail } from "./utils.ts" as utils yield utils.calculateAge(user.birthDate) >= 18 and utils.validateEmail(user.email) ``` -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - Functions are memoized per (function, args) when applicable; repeated calls with same args may return cached result. - Module scope: `use` is per policy; alias is used in that policy only. -## Constraints & Edge Cases - Missing or wrong-type arguments can cause runtime errors. See [Built-in TypeScript modules](/reference/typescript_modules/) and [Writing custom TypeScript modules](/extensibility/writing-custom-typescript-modules) for contracts. diff --git a/src/content/docs/reference/index.md b/src/content/docs/reference/index.md index 89b09c5..be58474 100644 --- a/src/content/docs/reference/index.md +++ b/src/content/docs/reference/index.md @@ -31,7 +31,9 @@ export shape IDENT **Composition:** [Policy composition](/language-concepts/policy-composition) (export/import) · [Writing custom TypeScript modules](/extensibility/writing-custom-typescript-modules) -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - One namespace per file; namespace must be the first statement (comments allowed before). - Policies must export at least one rule to be executable or importable. diff --git a/src/content/docs/reference/let.md b/src/content/docs/reference/let.md index ba9cf99..ec33518 100644 --- a/src/content/docs/reference/let.md +++ b/src/content/docs/reference/let.md @@ -4,16 +4,16 @@ description: let declaration syntax, scoping, and immutability. --- -`let` binds a name to an expression inside a block. It is scoped to that block, immutable, and cannot be exported. Used for intermediate calculations in policies and rules. +When you need to reuse a value or expression inside a policy or rule without repeating it, you use `let`. It binds a name to an expression inside a block, is scoped to that block, and is immutable. It cannot be exported—only used for intermediate calculations. -## Syntax +Here is the basic syntax: ```text let name = expr let name : type = expr ``` -## Reference +## Configuration & Arguments | Part | Type | Required | Description | | :--- | :--- | :--- | :--- | @@ -23,9 +23,9 @@ let name : type = expr **Returns:** N/A (binding). The name evaluates to the bound value in scope. -## Examples +## Examples in Action -### Basic Usage +### Binding a single intermediate value ```sentrie let adminRoles = ["admin", "super_admin"] @@ -33,7 +33,7 @@ let totalPrice = item.price * quantity let count: number = 10 ``` -### Advanced Usage +### Chaining let for readability ```sentrie let count: number @min(0) @max(100) = 50 @@ -41,13 +41,14 @@ let count: number @min(0) @max(100) = 50 Block scope: `let` inside a rule body is only visible in that body; policy-level `let` is visible to all rules in the policy. -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - Scoped to the immediate block (`{}`). Policy-level: visible to all rules in the policy. - Immutable: cannot be reassigned. - Cannot be exported; only rules can be exported. - Type annotation is optional; if present, value is validated at runtime. -## Constraints & Edge Cases - Same name in inner block shadows outer. Constraint validation failure aborts evaluation. diff --git a/src/content/docs/reference/membership-operations.md b/src/content/docs/reference/membership-operations.md index 477e302..4fef940 100644 --- a/src/content/docs/reference/membership-operations.md +++ b/src/content/docs/reference/membership-operations.md @@ -3,37 +3,41 @@ title: Membership Operations description: "Containment operators: in and contains (list, map, string)." --- +When you need to check whether a value appears in a list, a key exists in a map, or a substring appears in a string, you use `in` or `contains`. Both return a boolean. `needle in haystack` and `haystack contains needle` are equivalent—pick the one that reads better in your policy. -`in` and `contains` test whether a value is contained in a collection or string. Both return a boolean. `needle in haystack` and `haystack contains needle` are equivalent; use whichever reads more naturally. - -## Syntax +Here is the basic syntax: ```text needle in haystack haystack contains needle ``` -- **in:** Left = needle, right = haystack. True if haystack contains needle. -- **contains:** Left = haystack, right = needle. True if haystack contains needle. +**in:** Left = needle, right = haystack. True if haystack contains needle. **contains:** Left = haystack, right = needle. Same result; argument order reversed. + +## Configuration & Arguments -## Reference +You can test containment with these operators: -| Operator | Left | Right | Description | -| :--- | :--- | :--- | :--- | +| Operator | Left | Right | What it does | +| :------- | :--- | :---- | :----------- | | `in` | needle | haystack | True if haystack (string, list, or map) contains needle. | | `contains` | haystack | needle | Same as above; argument order reversed. | -**String:** Haystack and needle are strings. True if needle is a non-empty substring of haystack. +**String:** Both sides strings. True if needle is a non-empty substring of haystack. **List:** Haystack is a list; needle is any value. True if any element equals needle (by `==`). -**Map:** Haystack is a map. If needle is a string, true if that key exists. If needle is a map, true if haystack has all keys from needle and the corresponding values are equal. +**Map:** Haystack is a map. If needle is a string, true if that key exists. If needle is a map, true if haystack has all keys from needle with the same values. -**Returns:** bool. No trinary; invalid or unsupported types yield false (or key-mismatch for map subset). +**Returns:** bool. Invalid or unsupported types yield false (or key-mismatch for map subset). No trinary. + +--- -## Examples +## Examples in Action -### Basic Usage +### Checking substring and list membership + +You want to see if a role string is in a list of roles, or if a substring appears in a longer string. ```sentrie let in_str: bool = "foo" in "xfoobar" @@ -41,7 +45,9 @@ let has_role: bool = "admin" in user.roles let has_guest: bool = roles contains "guest" ``` -### Advanced Usage +### Checking map key presence and subset + +You need to know if a key exists on a map, or if one map is a “subset” of another (all keys present with matching values). ```sentrie let sub: bool = "admin" in roles @@ -49,15 +55,11 @@ let key_exists: bool = "plan" in subscription let subset: bool = {"a": 1, "b": 2} contains subscription ``` -## Behavior & Constraints +--- -- **String:** Needle must be non-empty string for a true result; empty needle → false. Substring is contiguous. -- **List:** Element-wise equality; no deep comparison of nested structures beyond `equals`. -- **Map (key):** Needle string checks key presence only. -- **Map (subset):** Needle map: every key in needle must exist in haystack with the same value; extra keys in haystack are allowed. +## Good to Know -## Constraints & Edge Cases +Before you implement this, keep a few boundaries in mind: -- Type mismatch (e.g. needle not string/list/map when haystack is map, or haystack not string/list/map) → false. -- For regex matching on strings, use [matches](/reference/boolean-operations) in Boolean Operations. -- Empty list or empty string haystack → false. For string containment, needle must be non-empty. +- **Constraint:** For strings, needle must be non-empty for a true result; empty needle → false. For lists, element-wise equality; no deep comparison beyond `equals`. For maps, needle string checks key presence; needle map requires every key in needle to exist in haystack with the same value; extra keys in haystack are allowed. +- **Edge case:** Type mismatch (e.g. haystack not string/list/map, or needle wrong type for map) → false. Empty list or empty string haystack → false. For regex matching on strings, use [matches](/reference/boolean-operations) in Boolean Operations. diff --git a/src/content/docs/reference/namespaces.md b/src/content/docs/reference/namespaces.md index d72b698..013d95b 100644 --- a/src/content/docs/reference/namespaces.md +++ b/src/content/docs/reference/namespaces.md @@ -3,48 +3,52 @@ title: Namespaces description: Namespace syntax and rules for organizing policies and shapes. --- +When you need to group policies and shapes and control which shapes are visible where, you put them under a namespace. Each `*.sentrie` file has exactly one namespace, and it must be the first statement. The namespace is the visibility boundary: unexported shapes are visible only within the same namespace. -Namespaces group policies and shapes and form the visibility boundary for unexported shapes. Each `*.sentrie` file has exactly one namespace and it must be the first statement. - -## Syntax +Here is the basic syntax: ```text namespace fully/qualified/name ``` -FQN is slash-separated identifiers (e.g. `com/example/auth`, `mycompany/policies/security`). +The name is slash-separated identifiers (e.g. `com/example/auth`, `mycompany/policies/security`). + +## Configuration & Arguments -## Reference +You declare a namespace with a single path: -| Element | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| FQN | identifier path | Yes | Slash-separated; valid identifiers only. | +| Argument | Type | Required | What it does | +| :------- | :--- | :------- | :----------- | +| FQN | identifier path | Yes | Slash-separated identifiers; forms the namespace for that file. | **Returns:** N/A (declaration). -## Examples +--- + +## Examples in Action -### Basic Usage +### Organizing policies by product or team + +You want a clear hierarchy so policies for auth, billing, or privacy live under different path segments. ```text namespace com/example/auth namespace com/example/billing/v2 ``` -### Advanced Usage +### Using a deep path for a specific feature + +You are grouping policies for a narrow feature (e.g. GDPR) under a long path. ```text namespace mycompany/policies/privacy/gdpr ``` -## Behavior & Constraints +--- -- Only comments may appear before the namespace declaration. -- One namespace per file. -- Namespace forms the visibility boundary: unexported shapes are visible only within the same namespace. -- Multiple root namespaces are allowed across a policy pack (different files). +## Good to Know -## Constraints & Edge Cases +Before you implement this, keep a few boundaries in mind: -- Namespace names must be valid identifiers. -- Child “namespaces” are not separate declarations; hierarchy is by naming convention (e.g. `a/b/c`). +- **Constraint:** Only comments may appear before the namespace declaration. One namespace per file. Multiple root namespaces are allowed across a pack (in different files). +- **Edge case:** Namespace names must be valid identifiers. Child “namespaces” are not separate declarations; hierarchy is by naming convention (e.g. `a/b/c`). No leading or trailing slash. diff --git a/src/content/docs/reference/policies.md b/src/content/docs/reference/policies.md index 1a21cd7..f219698 100644 --- a/src/content/docs/reference/policies.md +++ b/src/content/docs/reference/policies.md @@ -3,10 +3,9 @@ title: Policies description: "Policy syntax and allowed statements: facts, rules, let, use, export." --- +When you need to define a named set of inputs (facts), decision logic (rules), and optional helpers (`let`, `use`), you put them in a policy. A policy is a named block inside a namespace. It must have at least one rule and at least one `export decision of` so the policy can be run from the CLI or HTTP API. -A policy is a named block inside a namespace. It contains facts (inputs), rules (decisions), optional `let` bindings and `use` statements, and at least one `export decision of` rule. - -## Syntax +Here is the basic syntax: ```text policy IDENT { @@ -18,21 +17,27 @@ policy IDENT { } ``` -## Reference +## Configuration & Arguments + +You can structure a policy using these statements: -| Statement | Required | Description | -| :--- | :--- | :--- | +| Argument | Required | What it does | +| :------- | :------- | :----------- | | `fact` | No | Inputs; must appear before rules if present. | -| `let` | No | Intermediate values; scoped to block. | +| `let` | No | Intermediate values; scoped to the block. | | `use` | No | Import TypeScript functions. | | `rule` | Yes (≥1) | Decision logic. | | `export decision of` | Yes (≥1) | Exposes a rule for execution or import. | **Returns:** N/A (container). Evaluation returns the exported rule decision(s). -## Examples +--- + +## Examples in Action -### Basic Usage +### Defining a simple allow/deny policy + +You have one fact (e.g. user) and one rule that yields true or false; you export that rule so `sentrie exec` or the API can call it. ```sentrie namespace com/example/auth @@ -44,7 +49,9 @@ policy userAccess { } ``` -### Advanced Usage +### Using TypeScript modules, optional facts, and let + +You need a hash function, an optional context fact with a default, and a list of roles used in the rule. ```sentrie policy userAccess { @@ -59,13 +66,11 @@ policy userAccess { } ``` -## Behavior & Constraints +--- -- Facts must be declared before rules (only facts or comments may precede facts). -- At least one rule must be exported for the policy to be executable or importable. -- Rules in the same policy may reference each other without import. +## Good to Know -## Constraints & Edge Cases +Before you implement this, keep a few boundaries in mind: -- Policy name is an identifier. Same namespace may contain multiple policies. -- Exported rule names are the target for CLI/API and for `import decision of` from other policies. +- **Constraint:** Facts must be declared before rules (only facts or comments may precede facts). At least one rule must be exported for the policy to be executable or importable. Rules in the same policy may reference each other without import. +- **Edge case:** Policy name is an identifier. The same namespace may contain multiple policies. Exported rule names are the target for the CLI/API and for `import decision of` from other policies. diff --git a/src/content/docs/reference/precedence.md b/src/content/docs/reference/precedence.md index 17568b6..f8901d8 100644 --- a/src/content/docs/reference/precedence.md +++ b/src/content/docs/reference/precedence.md @@ -10,7 +10,7 @@ Operators are evaluated in order of precedence (highest first). Same precedence Expressions combine operators; precedence determines binding. Ternary `? :` is right-associative. -## Reference +## Configuration & Arguments | Precedence | Operators | Description | | :--- | :--- | :--- | @@ -27,9 +27,9 @@ Expressions combine operators; precedence determines binding. Ternary `? :` is r **Returns:** N/A (ordering rule). Result type is that of the top-level expression. -## Examples +## Examples in Action -### Basic Usage +### Typical use ```sentrie let result: number = 2 + 3 * 4 -- 14 @@ -37,17 +37,18 @@ let valid: bool = 5 + 3 > 7 -- true let complex: bool = true and false or true -- true ``` -### Advanced Usage +### Going further ```sentrie let value: number = 5 > 3 ? 10 : 20 -- 10 let nested: string = true ? (false ? "A" : "B") : "C" -- "B" ``` -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - Parentheses override precedence. Same level: left to right except ternary (right-associative). -## Constraints & Edge Cases - Use parentheses when in doubt to make intent explicit. diff --git a/src/content/docs/reference/rules.md b/src/content/docs/reference/rules.md index f36fee6..843c8a5 100644 --- a/src/content/docs/reference/rules.md +++ b/src/content/docs/reference/rules.md @@ -4,7 +4,9 @@ description: Rule syntax, evaluation (when/default/body), and outcome (trinary o --- -A rule defines a decision: an optional `when` condition, an optional `default`, and a body that must contain `yield`. If `when` is truthy the body is evaluated; otherwise the `default` (or `unknown`) is used. +When you need to define a single decision (e.g. allow/deny or a computed value) that can depend on a condition and a fallback, you write a rule. A rule has an optional `when` guard, an optional `default`, and a body that must contain `yield`. If `when` is truthy the body is evaluated; otherwise the `default` (or `unknown`) is used. + +Here is the basic syntax: ## Syntax @@ -12,7 +14,7 @@ A rule defines a decision: an optional `when` condition, an optional `default`, rule IDENT = [ default expr ] [ when expr ] { stmt* yield expr } ``` -## Reference +## Configuration & Arguments | Part | Type | Required | Description | | :--- | :--- | :--- | :--- | @@ -22,16 +24,16 @@ rule IDENT = [ default expr ] [ when expr ] { stmt* yield expr } **Returns:** The `yield` expression value when body runs, else `default` (or `unknown`). Type is trinary or any value type. -## Examples +## Examples in Action -### Basic Usage +### Defining a single rule with a default ```sentrie rule allow = default false { yield true } rule isAdmin = default false when user.role is defined { yield user.role == "admin" } ``` -### Advanced Usage +### Using when and TypeScript in a rule ```sentrie rule getPrice = default 0 when product.price is defined { @@ -41,13 +43,14 @@ rule getPrice = default 0 when product.price is defined { } ``` -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - Evaluation: `is_truthy(when) ? body_result : default`. Truthy follows [trinary](/reference/trinary) semantics. - Body must yield exactly once when evaluated. Only the chosen branch (body or default) is evaluated. - Rules in the same policy can reference other rules by name. Exported rules can be imported elsewhere. -## Constraints & Edge Cases - If no `default` and `when` is not truthy, outcome is `unknown`. - Recursion in rule evaluation is bounded by the language (no unbounded recursion). diff --git a/src/content/docs/reference/security-and-permissions.md b/src/content/docs/reference/security-and-permissions.md index 77b7a81..7ff53c9 100644 --- a/src/content/docs/reference/security-and-permissions.md +++ b/src/content/docs/reference/security-and-permissions.md @@ -15,7 +15,7 @@ net = ["host1.com", "host2.com"] env = ["VAR1", "VAR2"] ``` -## Reference +## Configuration & Arguments | Key | Type | Description | | :--- | :--- | :--- | @@ -25,16 +25,16 @@ env = ["VAR1", "VAR2"] **Returns:** N/A (configuration). Violations (e.g. reading outside fs_read) cause runtime failure. -## Examples +## Examples in Action -### Basic Usage +### Typical use ```toml [permissions] fs_read = ["."] ``` -### Advanced Usage +### Going further ```toml [permissions] @@ -43,11 +43,12 @@ net = ["example.com"] env = ["ORG_DSN", "REDIS_PASSWORD"] ``` -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - By default: filesystem access is limited to the policy pack root; no network; no environment variables. - Explicit entries grant access only to listed paths/hosts/vars. Modules run with the same permissions as the pack. -## Constraints & Edge Cases - Invalid or missing paths/hosts may be rejected at load or runtime. Restrict permissions to the minimum required. diff --git a/src/content/docs/reference/shapes.md b/src/content/docs/reference/shapes.md index db6af92..353fef6 100644 --- a/src/content/docs/reference/shapes.md +++ b/src/content/docs/reference/shapes.md @@ -21,7 +21,7 @@ shape Name { **Composition:** `shape Child with Base { ... }` -## Reference +## Configuration & Arguments | Modifier | Description | | :--- | :--- | @@ -32,9 +32,9 @@ shape Name { **Returns:** N/A (type definition). Values are validated when assigned or passed as facts. -## Examples +## Examples in Action -### Basic Usage +### Typical use ```sentrie shape User { @@ -44,7 +44,7 @@ shape User { } ``` -### Advanced Usage +### Going further ```sentrie shape Base { id!: string } @@ -52,11 +52,12 @@ shape Extended with Base { role!: string } shape ID string @uuid() ``` -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - Composed shape includes all fields of Base plus its own. Circular composition is not allowed. - Optional fields: use `is defined` to check before use. Exported shapes are visible across namespaces. -## Constraints & Edge Cases - Unexported shapes are namespace-local. Field order in literals does not affect type checking. diff --git a/src/content/docs/reference/trinary.md b/src/content/docs/reference/trinary.md index 3a63685..80ab608 100644 --- a/src/content/docs/reference/trinary.md +++ b/src/content/docs/reference/trinary.md @@ -3,32 +3,28 @@ title: Trinary Values description: "Three-valued logic: true, false, unknown; truthiness and Kleene tables." --- +Sentrie uses three values for logic and conditions: `true`, `false`, and `unknown`. Unknown covers cases where the result is indeterminate (e.g. an undefined field). When you write `when` guards or use ternary/Elvis, only `true` is treated as truthy; logical operators follow Kleene’s three-valued logic so unknown propagates in predictable ways. -Sentrie uses trinary logic: `true`, `false`, and `unknown`. Unknown represents indeterminate truth (e.g. undefined field access). Logical operators follow Kleene's three-valued logic. +Here is the basic syntax: -## Syntax - -Literals: `true` | `false` | `unknown` +Literals: `true` | `false` | `unknown` Logical: `and` | `or` | `xor` | `not` | `!` -**Returns:** Trinary. For `when` and conditionals: only `true` is truthy; `false` and `unknown` are not. - -## Concepts +## Configuration & Arguments -Truthiness: only `true` is truthy. `false` and `unknown` are non-truthy (e.g. for `when`, ternary, Elvis). +Truthiness and logical outcomes work like this: -## Examples +- **Truthiness:** Only `true` is truthy. `false` and `unknown` are non-truthy (e.g. for `when`, ternary, Elvis). +- **NOT:** One operand; result is trinary. -### Basic Usage - -```sentrie -let a = true and unknown -- unknown -let b = false or unknown -- unknown -let c = not unknown -- unknown -``` +| **Input** | Output | +| --- | --- | +| **true** | false | +| **false** | true | +| **unknown** | unknown | -### Kleene AND +**Kleene AND** | **AND** | true | false | unknown | | --- | --- | --- | --- | @@ -36,7 +32,7 @@ let c = not unknown -- unknown | **false** | false | false | false | | **unknown** | unknown | false | unknown | -### Kleene OR +**Kleene OR** | **OR** | true | false | unknown | | --- | --- | --- | --- | @@ -44,19 +40,33 @@ let c = not unknown -- unknown | **false** | true | false | unknown | | **unknown** | true | unknown | unknown | -### NOT +**Returns:** Trinary. For `when` and conditionals, only `true` is truthy; `false` and `unknown` are not. -| **Input** | Output | -| --- | --- | -| **true** | false | -| **false** | true | -| **unknown** | unknown | +--- + +## Examples in Action + +### Propagating unknown in expressions + +You are chaining conditions and want to see how `unknown` behaves so your rules don’t accidentally allow or deny when data is missing. + +```sentrie +let a = true and unknown -- unknown +let b = false or unknown -- unknown +let c = not unknown -- unknown +``` -## Behavior & Constraints +### Using unknown in a rule when + +You rely on a `when` guard; if the condition is unknown (e.g. optional field missing), the rule should fall back to its default instead of treating it as true. + +In practice: `unknown` in a `when` causes the rule to use its default (or an `unknown` outcome). `not unknown` remains `unknown`. + +--- -- Undefined field access yields unknown; operations on unknown propagate unknown per Kleene tables. -- Rule `when` and ternary/Elvis use truthiness: only `true` is truthy. +## Good to Know -## Constraints & Edge Cases +Before you implement this, keep a few boundaries in mind: -- `unknown` in a `when` causes the rule to use its default (or `unknown` outcome). `not unknown` is `unknown`. +- **Constraint:** Undefined field access yields `unknown`; operations on `unknown` propagate according to the Kleene tables above. Rule `when` and ternary/Elvis use truthiness: only `true` is truthy. +- **Edge case:** If a `when` evaluates to `unknown`, the rule uses its default or returns `unknown`. `not unknown` is `unknown`. diff --git a/src/content/docs/reference/types-and-values.md b/src/content/docs/reference/types-and-values.md index 1c36bf1..e122260 100644 --- a/src/content/docs/reference/types-and-values.md +++ b/src/content/docs/reference/types-and-values.md @@ -16,7 +16,7 @@ Sentrie provides primitive types (`number`, `string`, `trinary`, `bool`, `docume List index: `expr[number]`. Map index: `expr[string]` or `expr.key`. -## Reference +## Configuration & Arguments | Type | Description | | :--- | :--- | @@ -31,9 +31,9 @@ List index: `expr[number]`. Map index: `expr[string]` or `expr.key`. **Returns:** N/A for types. `cast` returns the value after validation against the target type (and constraints); fails if invalid. -## Examples +## Examples in Action -### Basic Usage +### Typical use ```text let u: number = 50 @@ -44,7 +44,7 @@ let m: map[number] = { "one": 1, "two": 2 } let r: record[string, number, bool] = ["one", 1, true] ``` -### Advanced Usage +### Going further ```text let first: number = arr[0] @@ -53,12 +53,13 @@ let oneAlt: number = m.one let x: number = cast "50" as number ``` -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - Type annotation on `let` is optional; when omitted, value is not validated against a type. - Map keys must be strings. Division by zero aborts evaluation. Constraint failure aborts evaluation. -## Constraints & Edge Cases - Map keys must be strings. Access with `[index]`: number for list/record, string for map. - `cast` validates against the target type and any constraints; failure aborts evaluation. diff --git a/src/content/docs/reference/typescript_modules/index.md b/src/content/docs/reference/typescript_modules/index.md index 99a94cd..5893811 100644 --- a/src/content/docs/reference/typescript_modules/index.md +++ b/src/content/docs/reference/typescript_modules/index.md @@ -14,7 +14,7 @@ use { fn1, fn2 } from @sentrie/module [ as alias ] Built-in modules: `@sentrie/module` (no quotes). Default alias is the last path segment (e.g. `time` for `@sentrie/time`). -## Reference +## Configuration & Arguments | Element | Type | Required | Description | | :--- | :--- | :--- | :--- | @@ -24,9 +24,9 @@ Built-in modules: `@sentrie/module` (no quotes). Default alias is the last path **Returns:** N/A (import). Function return types are per module; see linked pages. -## Examples +## Examples in Action -### Basic Usage +### Typical use ```text use { now } from @sentrie/time @@ -34,7 +34,7 @@ use { sha256 } from @sentrie/hash use { isValid } from @sentrie/json as jsonUtil ``` -### Advanced Usage +### Going further ```text use { now } from @sentrie/time @@ -63,10 +63,11 @@ let ok = jsonUtil.isValid(data) | [@sentrie/url](/reference/typescript_modules/sentrie/url) | parse, join, getHost, getPath, getQuery, isValid | | [@sentrie/uuid](/reference/typescript_modules/sentrie/uuid) | v4, v6, v7 | -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - Only the listed functions can be imported. Paths are resolved at load time. Invalid function names or modules cause errors. -## Constraints & Edge Cases - Built-in `@sentrie/*` modules do not use quotes. Local files use quoted paths (e.g. `"./utils.ts"`). JWT module only decodes/verifies; it does not create tokens. Prefer SHA-256/SHA-512 over MD5/SHA-1 for security. diff --git a/src/content/docs/reference/typescript_modules/sentrie/collection.md b/src/content/docs/reference/typescript_modules/sentrie/collection.md index d3ffec4..8aaea7f 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/collection.md +++ b/src/content/docs/reference/typescript_modules/sentrie/collection.md @@ -333,10 +333,11 @@ policy mypolicy { } ``` -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - List functions use `list_` prefix; map functions use `map_` prefix. Operations do not mutate the original collection. -## Constraints & Edge Cases - Throws if input is not an array (for list_*) or not an object (for map_*). Deep equality used where applicable. diff --git a/src/content/docs/reference/typescript_modules/sentrie/crypto.md b/src/content/docs/reference/typescript_modules/sentrie/crypto.md index 41b9a3d..b2eb68c 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/crypto.md +++ b/src/content/docs/reference/typescript_modules/sentrie/crypto.md @@ -45,10 +45,11 @@ policy mypolicy { } ``` -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - Returns SHA-256 hash as hexadecimal string. For more algorithms use [@sentrie/hash](/reference/typescript_modules/sentrie/hash). -## Constraints & Edge Cases - Input must be string. Invalid or unsupported input may throw. diff --git a/src/content/docs/reference/typescript_modules/sentrie/encoding.md b/src/content/docs/reference/typescript_modules/sentrie/encoding.md index 796b3e1..f107763 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/encoding.md +++ b/src/content/docs/reference/typescript_modules/sentrie/encoding.md @@ -178,10 +178,11 @@ policy mypolicy { } ``` -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - Base64/hex/URL encode decode; input and output are strings. Invalid Base64/hex input may throw. -## Constraints & Edge Cases - base64Url omits padding; URL encoding is percent-encoding. Decode errors throw. diff --git a/src/content/docs/reference/typescript_modules/sentrie/hash.md b/src/content/docs/reference/typescript_modules/sentrie/hash.md index d0c4a36..8e9145a 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/hash.md +++ b/src/content/docs/reference/typescript_modules/sentrie/hash.md @@ -136,11 +136,12 @@ policy mypolicy { } ``` -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - All hash functions return hexadecimal-encoded strings. Input is string; HMAC takes algorithm name, message, and secret key. - Use SHA-256 or SHA-512 for security; MD5 and SHA-1 are cryptographically broken. -## Constraints & Edge Cases - Invalid input (e.g. non-hex for decode) may throw. HMAC algorithm must be supported (e.g. sha256). diff --git a/src/content/docs/reference/typescript_modules/sentrie/js.md b/src/content/docs/reference/typescript_modules/sentrie/js.md index e2cc519..5ebeb19 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/js.md +++ b/src/content/docs/reference/typescript_modules/sentrie/js.md @@ -474,10 +474,11 @@ policy mypolicy { } ``` -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - Exposes Math, String, Number, Date, JSON, Array as individual functions. Behavior matches JavaScript; angles in radians for trig. -## Constraints & Edge Cases - Invalid input (e.g. NaN, invalid date) may produce NaN or throw per JS semantics. random() is not cryptographically secure. diff --git a/src/content/docs/reference/typescript_modules/sentrie/json.md b/src/content/docs/reference/typescript_modules/sentrie/json.md index e513c78..f351136 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/json.md +++ b/src/content/docs/reference/typescript_modules/sentrie/json.md @@ -13,7 +13,7 @@ use { isValid } from @sentrie/json [ as alias ] alias.isValid(str) ``` -## Reference +## Configuration & Arguments | Name | Type | Required | Description | | :---- | :----- | :------- | :-------------------------- | @@ -21,9 +21,9 @@ alias.isValid(str) **Returns:** `boolean` - true if the string is valid JSON, false otherwise. -## Examples +## Examples in Action -### Basic Usage +### Typical use ```text use { isValid } from @sentrie/json as jsonUtil @@ -31,7 +31,7 @@ let ok = jsonUtil.isValid('{"name": "John", "age": 30}') let bad = jsonUtil.isValid('{"name": "John", "age":}') ``` -### Advanced Usage +### Going further ```text use { isValid } from @sentrie/json as jsonUtil @@ -41,10 +41,11 @@ rule processData = default false { } ``` -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - Only validates syntax; does not parse. Use `@sentrie/js` for `parse` and `stringify`. -## Constraints & Edge Cases - Empty string is not valid JSON. Invalid UTF-8 or malformed structure returns false. diff --git a/src/content/docs/reference/typescript_modules/sentrie/jwt.md b/src/content/docs/reference/typescript_modules/sentrie/jwt.md index 38634b0..68bf82d 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/jwt.md +++ b/src/content/docs/reference/typescript_modules/sentrie/jwt.md @@ -150,10 +150,11 @@ header.payload.signature - **Payload** - Contains the claims (data) - **Signature** - Used to verify the token hasn't been tampered with -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - Decode and verify only; does not create tokens. Supported algorithms: HS256, HS384, HS512. Always verify signature before trusting payload. -## Constraints & Edge Cases - Invalid token format, bad signature, or unsupported algorithm throws. Use verify() or decode() with secret; do not rely on getPayload() alone. diff --git a/src/content/docs/reference/typescript_modules/sentrie/net.md b/src/content/docs/reference/typescript_modules/sentrie/net.md index 52311ed..31b258a 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/net.md +++ b/src/content/docs/reference/typescript_modules/sentrie/net.md @@ -258,10 +258,11 @@ policy mypolicy { } ``` -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - IPv4 and IPv6; CIDR and single IP. parseIP returns parsed representation or null. Invalid CIDR/IP throws. -## Constraints & Edge Cases - Invalid CIDR or IP throws. Use isPrivate, isPublic, isLoopback for classification. diff --git a/src/content/docs/reference/typescript_modules/sentrie/regex.md b/src/content/docs/reference/typescript_modules/sentrie/regex.md index f93f424..db24149 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/regex.md +++ b/src/content/docs/reference/typescript_modules/sentrie/regex.md @@ -200,10 +200,11 @@ let alnumPattern = "^[a-zA-Z0-9_]+$" - Repeated use of the same pattern in a single execution context will use the cached compiled pattern - Patterns are automatically cleaned up after execution -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - Patterns are strings; compiled and cached per execution. match/find/findAll/replace/replaceAll/split follow standard regex semantics. -## Constraints & Edge Cases - Invalid pattern throws. Replace/split return new string/list; original unchanged. diff --git a/src/content/docs/reference/typescript_modules/sentrie/semver.md b/src/content/docs/reference/typescript_modules/sentrie/semver.md index 5be05d1..a0a4371 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/semver.md +++ b/src/content/docs/reference/typescript_modules/sentrie/semver.md @@ -234,10 +234,11 @@ Semantic versions follow the format: `MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]` - **PRERELEASE** - Optional prerelease identifier (e.g., `alpha`, `beta`, `rc.1`) - **BUILD** - Optional build metadata (e.g., `+001`, `+exp.sha.5114f85`) -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - "v" prefix is stripped; compare returns -1, 0, 1. satisfies uses npm-style range. major/minor/patch/prerelease/metadata extract components. -## Constraints & Edge Cases - Invalid version string throws. stripPrefix removes leading "v". diff --git a/src/content/docs/reference/typescript_modules/sentrie/time.md b/src/content/docs/reference/typescript_modules/sentrie/time.md index 427d1d2..5344052 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/time.md +++ b/src/content/docs/reference/typescript_modules/sentrie/time.md @@ -243,10 +243,11 @@ Common format patterns: - `"2006-01-02 15:04:05"` - Date and time: `2006-01-02 15:04:05` - `"Mon, 02 Jan 2006 15:04:05 MST"` - RFC1123 format -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - Timestamps are Unix seconds. now() is fixed within an execution. addDuration/subtractDuration use duration strings (e.g. "1h", "30m"). isBefore/isAfter/isBetween compare timestamps. -## Constraints & Edge Cases - Invalid format or duration may throw. Constants (RFC3339, etc.) are format layout strings. diff --git a/src/content/docs/reference/typescript_modules/sentrie/url.md b/src/content/docs/reference/typescript_modules/sentrie/url.md index 8e30f13..a5613b8 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/url.md +++ b/src/content/docs/reference/typescript_modules/sentrie/url.md @@ -194,10 +194,11 @@ policy mypolicy { - The `parse` function provides comprehensive URL parsing with all components - The `join` function properly resolves relative paths against base URLs -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - parse returns ParsedURL; getHost/getPath/getQuery work on string URLs. Encoding/decoding is in @sentrie/encoding. -## Constraints & Edge Cases - Invalid URL may throw or return invalid result. isValid checks format. Relative URLs (starting with /) supported. diff --git a/src/content/docs/reference/typescript_modules/sentrie/uuid.md b/src/content/docs/reference/typescript_modules/sentrie/uuid.md index 1db33d5..3ae0f6f 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/uuid.md +++ b/src/content/docs/reference/typescript_modules/sentrie/uuid.md @@ -15,7 +15,7 @@ alias.v6() alias.v7() ``` -## Reference +## Configuration & Arguments | Function | Parameters | Required | Description | | :------- | :--------- | :------- | :------------------------------------------------- | @@ -25,9 +25,9 @@ alias.v7() **Returns:** `string` - UUID in form `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`. Throws on generation failure. -## Examples +## Examples in Action -### Basic Usage +### Typical use ```text use { v4, v7 } from @sentrie/uuid @@ -35,7 +35,7 @@ let id = uuid.v4() let timeId = uuid.v7() ``` -### Advanced Usage +### Going further ```text use { v4, v7 } from @sentrie/uuid @@ -46,10 +46,11 @@ rule createResource = default false { } ``` -## Behavior & Constraints +## Good to Know + +Before you implement this, keep a few boundaries in mind: - v4: random; no ordering. v6/v7: time-ordered for better DB indexing/sorting. v7 includes Unix timestamp. -## Constraints & Edge Cases - UUIDs are not cryptographically secure; use proper CSPRNG for security-sensitive randomness. Generation failure throws. From a0ae55d3054e7b77fb893d02aa75554bc9ec85c0 Mon Sep 17 00:00:00 2001 From: Binaek Sarkar Date: Sat, 21 Feb 2026 23:48:14 +0530 Subject: [PATCH 10/21] Enhance constraints documentation with comprehensive details and examples --- src/content/docs/reference/constraints.md | 154 ++++++++++++++++++++-- src/content/docs/reference/trinary.md | 1 + 2 files changed, 141 insertions(+), 14 deletions(-) diff --git a/src/content/docs/reference/constraints.md b/src/content/docs/reference/constraints.md index 1479aa2..242c26c 100644 --- a/src/content/docs/reference/constraints.md +++ b/src/content/docs/reference/constraints.md @@ -1,9 +1,8 @@ --- title: Constraints -description: Constraint syntax and validation for types (e.g. @min, @max, @email). +description: Exhaustive reference for constraint syntax and all constraints (e.g. @min, @max, @email) on number, string, list, and trinary types. --- - Constraints validate values at runtime using the `@` syntax on types. They apply to primitives, collection elements, and shape fields. Validation failure aborts evaluation. ## Syntax @@ -12,18 +11,124 @@ Constraints validate values at runtime using the `@` syntax on types. They apply type @constraint1(args) @constraint2 ``` -Examples: `number @min(0) @max(100)`, `string @email`, `list[string @one_of("a","b")]`. +Examples: `number @min(0) @max(100)`, `string @email`, `list[string @one_of("a","b")]`. Multiple constraints can be chained; all must pass. Constraint arguments use the same literal syntax as the language (numbers, strings, lists where applicable). + +## When constraints are checked + +Constraints are checked: + +- When a value is **assigned** to a variable or field (e.g. `let x: number @min(0) = 50` or a shape field). +- When a value is **cast** to a constrained type (e.g. `cast 50 as number @min(0) @max(100)`). +- When **facts** are supplied and matched to shape types that have constraints. + +If any constraint fails, evaluation aborts with an error. Order of application is defined by the runtime; all specified constraints must pass. ## Configuration & Arguments -| Category | Constraints (examples) | -| :--- | :--- | -| Numeric | `@eq`, `@neq`, `@gt`, `@lt`, `@in`, `@not_in`, `@range(min,max)`, `@multiple_of`, `@even`, `@odd`, `@positive`, `@negative`, `@non_negative`, `@non_positive`, `@finite`, `@infinite`, `@nan` | -| String | `@length`, `@minlength`, `@maxlength`, `@regexp`, `@starts_with`, `@ends_with`, `@has_substring`, `@not_has_substring`, `@email`, `@url`, `@uuid`, `@alphanumeric`, `@alpha`, `@numeric`, `@lowercase`, `@uppercase`, `@trimmed`, `@not_empty`, `@one_of`, `@not_one_of` | -| List | `@not_empty` | +### Numeric constraints (`number`) + +| Constraint | Arguments | Description | +| :-------------------- | :------------------- | :-------------------------------------------------------------------------- | +| `@min(value)` | 1 (number) | Value must be greater than or equal to the specified value. | +| `@max(value)` | 1 (number) | Value must be less than or equal to the specified value. | +| `@eq(value)` | 1 (number) | Value must equal the specified value. | +| `@neq(value)` | 1 (number) | Value must not equal the specified value. | +| `@gt(value)` | 1 (number) | Value must be greater than the specified value. | +| `@lt(value)` | 1 (number) | Value must be less than the specified value. | +| `@in(values...)` | 1 (list) or variadic | Value must be in the specified list of numbers. | +| `@not_in(values...)` | 1 (list) or variadic | Value must not be in the specified list of numbers. | +| `@range(min, max)` | 2 (numbers) | Value must be between min and max (inclusive). | +| `@multiple_of(value)` | 1 (number) | Value must be a multiple of the specified value (divisor must not be zero). | +| `@even()` | 0 | Value must be even. | +| `@odd()` | 0 | Value must be odd. | +| `@positive()` | 0 | Value must be positive (> 0). | +| `@negative()` | 0 | Value must be negative (< 0). | +| `@non_negative()` | 0 | Value must be non-negative (≥ 0). | +| `@non_positive()` | 0 | Value must be non-positive (≤ 0). | + +### Number-specific constraints (`number`) + +Numbers are represented as float64, which can represent ±∞ and NaN. Sentrie **does not produce** these values: there is no infinity literal, and division by zero aborts evaluation. ±∞ and NaN can only appear when a value comes from a TypeScript function (e.g. JS `1/0` → `Infinity`) or from external input. Use these constraints to accept or reject such values: + +| Constraint | Arguments | Description | +| :------------ | :-------- | :---------------------------------------------------------------------------- | +| `@finite()` | 0 | Value must be finite (not ±∞ and not NaN). Use to reject TS/input infinities. | +| `@infinite()` | 0 | Value must be ±∞. Rare; only relevant when TS or input can supply infinity. | +| `@nan()` | 0 | Value must be NaN. Rare; only relevant when TS or input can supply NaN. | + +### String constraints (`string`) + +| Constraint | Arguments | Description | +| :------------------------------ | :----------- | :----------------------------------------------------------------------------------------------- | +| `@length(value)` | 1 (number) | String length must equal the specified value. | +| `@minlength(value)` | 1 (number) | String length must be at least the specified value. | +| `@maxlength(value)` | 1 (number) | String length must be at most the specified value. | +| `@regexp(pattern)` | 1 (string) | String must match the specified regular expression (Go regexp). Invalid pattern causes an error. | +| `@starts_with(substring)` | 1 (string) | String must start with the specified substring. | +| `@ends_with(substring)` | 1 (string) | String must end with the specified substring. | +| `@has_substring(substring)` | 1 (string) | String must contain the specified substring. | +| `@not_has_substring(substring)` | 1 (string) | String must not contain the specified substring. | +| `@email()` | 0 | String must be a valid email address (pattern-based). | +| `@url()` | 0 | String must be a valid URL (pattern-based). | +| `@uuid()` | 0 | String must be a valid UUID. | +| `@alphanumeric()` | 0 | String must contain only alphanumeric characters (letters and digits). | +| `@alpha()` | 0 | String must contain only letter characters. | +| `@numeric()` | 0 | String must be parseable as a numeric value. | +| `@lowercase()` | 0 | String must be lowercase. | +| `@uppercase()` | 0 | String must be uppercase. | +| `@trimmed()` | 0 | String must not have leading or trailing whitespace. | +| `@not_empty()` | 0 | String must not be empty. | +| `@one_of(values...)` | 1+ (strings) | String must be one of the specified values. | +| `@not_one_of(values...)` | 1+ (strings) | String must not be one of the specified values. | + +### List constraints (`list[...]`) + +| Constraint | Arguments | Description | +| :------------- | :-------- | :---------------------- | +| `@not_empty()` | 0 | List must not be empty. | + +### Trinary constraints (`trinary` / `bool`) + +| Constraint | Arguments | Description | +| :--------------- | :---------- | :---------------------------------------------------------------------- | +| `@not_unknown()` | 0 | Value must not be `unknown` (i.e. must be `true` or `false`). | +| `@eq(value)` | 1 (trinary) | Value must equal the specified trinary (`true`, `false`, or `unknown`). | +| `@neq(value)` | 1 (trinary) | Value must not equal the specified trinary. | +| `@is_true()` | 0 | Value must be `true`. | +| `@is_false()` | 0 | Value must be `false`. | **Returns:** N/A. Constraint failure raises an error and aborts evaluation. +## Collection constraints (element-level) + +Constraints can be applied to the **element type** of a list so that each element is validated: + +```text +list[string @one_of("read", "write", "delete")] +list[number @min(0) @max(100)] +``` + +For readability and reuse, define a [shape](/reference/shapes) and use it as the element type: + +```text +shape Permission string @one_of("read", "write", "delete") +let permissions: list[Permission] = ["read", "write"] +``` + +## Type conversion with constraints + +When converting with `cast expr as type`, the result is validated against the target type and **all constraints** on that type. If validation fails, evaluation aborts. + +```text +let u: number @min(0) @max(100) = cast "50" as number +``` + +```text +let u: string @email = cast "user@example.com" as string +``` + +Casting to a constrained type is equivalent to assigning the cast result to a variable of that type; the same constraint rules apply. + ## Examples in Action ### Typical use @@ -33,18 +138,39 @@ let u: number @min(0) @max(100) = 50 shape Permission string @one_of("read", "write", "delete") ``` -### Going further +Validation runs at assignment. For example, `let u: number @min(0) @max(100) = 101` would fail. + +### Collection and shapes ```text let permissions: list[string @one_of("read", "write", "delete")] = ["read", "write"] ``` -## Good to Know +Using a shape for the element type: -Before you implement this, keep a few boundaries in mind: +```text +shape Positive100 number @min(0) @max(100) +let y = 50 +let c: Positive100 = y +``` + +Here `y` is validated against `Positive100` (and thus `@min(0) @max(100)`) before assignment to `c`. -- Constraints are checked at runtime when a value is assigned or cast to the constrained type. -- Order of application is defined by the runtime. All specified constraints must pass. +### Trinary constraints +```text +let flag: trinary @is_true = true +let known: trinary @not_unknown = some_expression +``` + +## Good to Know + +Before you implement this, keep a few boundaries in mind: -- Failing constraint validation aborts evaluation immediately. Use shapes to reuse constrained types. +- Constraints are checked at runtime when a value is assigned or cast to the constrained type (and when facts are matched to constrained shape types). +- Order of application is defined by the runtime. All specified constraints must pass; failure aborts evaluation immediately. +- Use shapes to reuse constrained types (e.g. `shape ID string @uuid()`) and to name element types in collections. +- Numeric constraints use float64; `@even` and `@odd` use modulo. `@multiple_of` uses a small epsilon for floating-point remainder checks. +- **Infinity and NaN:** The language has no infinity or NaN literal, and division by zero aborts (it does not produce ±∞). So `@infinite()` and `@nan()` only apply when a number comes from TypeScript or external input (e.g. a JS function returning `Infinity`). +- String `@regexp` uses Go’s [regexp](https://pkg.go.dev/regexp) semantics. Invalid pattern strings cause an evaluation error. +- Map types currently have no built-in constraints; only list and element types (e.g. `list[T]` with constrained `T`) are covered above. diff --git a/src/content/docs/reference/trinary.md b/src/content/docs/reference/trinary.md index 80ab608..97d335d 100644 --- a/src/content/docs/reference/trinary.md +++ b/src/content/docs/reference/trinary.md @@ -70,3 +70,4 @@ Before you implement this, keep a few boundaries in mind: - **Constraint:** Undefined field access yields `unknown`; operations on `unknown` propagate according to the Kleene tables above. Rule `when` and ternary/Elvis use truthiness: only `true` is truthy. - **Edge case:** If a `when` evaluates to `unknown`, the rule uses its default or returns `unknown`. `not unknown` is `unknown`. +- For runtime validation of trinary values (e.g. require `true` or forbid `unknown`), see [Constraints](/reference/constraints) — trinary supports `@not_unknown()`, `@eq`, `@neq`, `@is_true()`, and `@is_false()`. From a2dc49a02345c06e42fb2346ae3b4f5fb4138faf Mon Sep 17 00:00:00 2001 From: Binaek Sarkar Date: Sun, 22 Feb 2026 13:58:47 +0530 Subject: [PATCH 11/21] Enhance documentation for arithmetic, boolean, collection, and fact operations with comprehensive details and examples --- .../docs/reference/arithmetic-operations.md | 69 +++++--- .../docs/reference/boolean-operations.md | 76 ++++---- .../docs/reference/collection-operations.md | 60 ++++--- src/content/docs/reference/facts.md | 66 ++++--- src/content/docs/reference/functions.md | 56 +++--- src/content/docs/reference/index.md | 54 ++++-- src/content/docs/reference/let.md | 77 ++++++-- .../docs/reference/membership-operations.md | 59 ++++--- src/content/docs/reference/namespaces.md | 64 +++++-- src/content/docs/reference/policies.md | 69 +++++--- src/content/docs/reference/precedence.md | 79 ++++++--- src/content/docs/reference/rules.md | 70 +++++--- .../reference/security-and-permissions.md | 63 ++++--- src/content/docs/reference/shapes.md | 167 +++++++++++++++--- src/content/docs/reference/trinary.md | 103 +++++++---- .../docs/reference/types-and-values.md | 77 +++++--- 16 files changed, 826 insertions(+), 383 deletions(-) diff --git a/src/content/docs/reference/arithmetic-operations.md b/src/content/docs/reference/arithmetic-operations.md index f8620d5..ed43120 100644 --- a/src/content/docs/reference/arithmetic-operations.md +++ b/src/content/docs/reference/arithmetic-operations.md @@ -1,38 +1,37 @@ --- title: Arithmetic Operations -description: "Arithmetic operators: +, -, *, /, %; types and edge cases." +description: Exhaustive reference for arithmetic operators (+, -, *, /, %), unary +/-, operand types, and edge cases including division by zero. --- - -Arithmetic operators operate on `number` (float64). All numeric operands are unified as `number`. Result type is `number`. +Arithmetic operators operate on `number` (float64). All numeric operands are treated as `number`; there is no separate integer type. The result of every arithmetic operation is `number`. Division by zero and modulo by zero abort evaluation with an error. ## Syntax -```text -expr + expr -expr - expr -expr * expr -expr / expr -expr % expr -``` +**Binary:** `expr + expr` | `expr - expr` | `expr * expr` | `expr / expr` | `expr % expr` -Unary: `+ expr` | `- expr` +**Unary:** `+ expr` | `- expr` ## Configuration & Arguments -| Operator | Description | Result | -| :--- | :--- | :--- | -| `+` | Addition | number | -| `-` | Subtraction | number | -| `*` | Multiplication | number | -| `/` | Division | number | -| `%` | Modulo (remainder) | number | +| Operator | Description | Operands | Result | Edge case | +| :------- | :---------- | :------- | :----- | :-------- | +| `+` | Addition | number, number | number | — | +| `-` | Subtraction | number, number | number | — | +| `*` | Multiplication | number, number | number | — | +| `/` | Division | number, number | number | Divisor zero → error; evaluation aborts. | +| `%` | Modulo (remainder) | number, number | number | Divisor zero → error; evaluation aborts. | +| unary `+` | Unary plus | number | number | — | +| unary `-` | Unary minus (negation) | number | number | — | + +**Returns:** `number`. Division or modulo by zero aborts evaluation. -**Returns:** `number`. Division by zero aborts evaluation. +## Operand types and conversion + +All operands are interpreted as `number`. Mixed integer/float literals are allowed; they are unified as float64. Division is floating-point (e.g. `7 / 2` is `3.5`). Modulo uses the remainder after division; the exact behavior for negative operands follows the implementation (typically Go’s `math.Mod` or similar). ## Examples in Action -### Typical use +### Basic arithmetic ```sentrie let sum: number = 5 + 3 @@ -42,19 +41,35 @@ let quot: number = 15 / 3 let rem: number = 10 % 3 ``` -### Going further +### Division and float result + +```sentrie +let half: number = 7 / 2 -- 3.5 +``` + +### Unary minus + +```sentrie +let neg: number = -x +let pos: number = +y +``` + +### Guarding against division by zero ```sentrie -let area: number = rect.width * rect.height let safe: number = divisor != 0 ? 10 / divisor : 0.0 ``` -## Good to Know +### Using arithmetic in expressions -Before you implement this, keep a few boundaries in mind: +```sentrie +let area: number = rect.width * rect.height +``` -- All operands are `number`; mixed integer/float is allowed. Division is float (e.g. 7/2 = 3.5). -- Modulo: remainder after division; divisor zero aborts. +## Good to Know +Before you implement this, keep a few boundaries in mind: -- Division by zero aborts evaluation. Use a guard (e.g. ternary) to avoid. +- **Operands:** All operands are `number` (float64). Mixed integer/float literals are allowed; there is no separate integer type. +- **Division:** Result is float (e.g. 7/2 = 3.5). Division by zero aborts evaluation. Use a guard (e.g. ternary or `when`) to avoid dividing by zero. +- **Modulo:** Remainder after division. Divisor zero aborts evaluation. Behavior for negative numbers is implementation-defined (e.g. Go’s `math.Mod`). diff --git a/src/content/docs/reference/boolean-operations.md b/src/content/docs/reference/boolean-operations.md index 7b919d5..a7f2a2e 100644 --- a/src/content/docs/reference/boolean-operations.md +++ b/src/content/docs/reference/boolean-operations.md @@ -1,74 +1,86 @@ --- title: Boolean Operations -description: Logical (and, or, xor, not), comparison (==, !=, <, <=, >, >=), pattern (matches), and conditional operators. +description: Exhaustive reference for logical (and, or, xor, not), comparison (==, !=, <, <=, >, >=), pattern (matches), and conditional (ternary, Elvis) operators. --- -When you need to combine conditions, compare values, or branch on truthiness, you use Sentrie’s logical, comparison, pattern, and conditional operators. They keep a consistent story: logical and comparison ops use [trinary](/reference/trinary) semantics (true, false, unknown), and the pattern operator `matches` works on strings only. +Boolean operations combine conditions, compare values, and branch on truthiness. Logical and comparison operators use [trinary](/reference/trinary) semantics (true, false, unknown). The pattern operator `matches` works on strings and returns a boolean. The ternary (`? :`) and Elvis (`?:`) operators choose a value based on whether a condition is truthy; only `true` is truthy. -Here is the basic syntax: +## Syntax -**Logical (binary):** `and` | `or` | `xor` +**Logical (binary):** `and` | `or` | `xor` -**Negation (unary):** `not expr` | `! expr` +**Negation (unary):** `not expr` | `! expr` -**Comparison:** `==` | `!=` | `is` | `is not` | `<` | `<=` | `>` | `>=` +**Comparison:** `==` | `!=` | `is` | `is not` | `<` | `<=` | `>` | `>=` -**Pattern:** `string matches pattern` (pattern is a regex string) +**Pattern:** `stringExpr matches patternExpr` (both operands strings; right is a regex pattern) **Conditional:** `condition ? trueValue : falseValue` | `expr ?: default` ## Configuration & Arguments -You can combine and compare values using these operators: +| Operator | Operands | What it does | Returns | +| :------------------- | :------------------------------- | :----------------------------------------------------------------------------------------------------------------------------- | :---------------------- | +| `and` | expr, expr | Logical AND (Kleene). Short-circuits: if left is false, right is not evaluated. | trinary | +| `or` | expr, expr | Logical OR (Kleene). Short-circuits: if left is true, right is not evaluated. | trinary | +| `xor` | expr, expr | Logical XOR. True when exactly one operand is truthy. | trinary | +| `not`, `!` | expr | Logical NOT (unary). One operand; converted to trinary then negated (true↔false; unknown→unknown). | trinary | +| `==`, `is` | expr, expr | Equality. Both sides must be comparable. | trinary | +| `!=`, `is not` | expr, expr | Inequality. | trinary | +| `<`, `<=`, `>`, `>=` | expr, expr | Ordering. Both sides must be comparable (e.g. number, string). | trinary | +| `matches` | string, string | Left: value; right: regex pattern (Go [regexp](https://pkg.go.dev/regexp)). Invalid pattern → error. | bool | +| `? :` | condition, trueValue, falseValue | Ternary: if condition is truthy, result is trueValue; else falseValue. Only the chosen branch is evaluated. Right-associative. | type of chosen branch | +| `?:` | expr, default | Elvis: if expr is truthy, result is expr; else result is default. Non-truthy: false, unknown, null, 0, "", empty collection. | type of expr or default | -| Operator | What it does | Returns | -| :------- | :----------- | :------ | -| `and` | Logical AND (Kleene) | trinary | -| `or` | Logical OR (Kleene) | trinary | -| `xor` | Logical XOR | trinary | -| `not`, `!` | Logical NOT (unary). Single operand; converted to trinary then negated. | trinary | -| `==`, `is` | Equality | trinary | -| `!=`, `is not` | Inequality | trinary | -| `<`, `<=`, `>`, `>=` | Ordering | trinary | -| `matches` | String matches regex (left: string, right: pattern). Invalid regex → error. | bool | -| `? :` | Ternary: pick trueValue or falseValue by condition | type of chosen branch | -| `?:` | Elvis: use expression if truthy, else default | type | +**Returns:** Trinary for logical and comparison; bool for `matches`; type of the chosen value for ternary and Elvis. -**Returns:** Trinary or the selected value. For Elvis, non-truthy means `false`, `unknown`, `null`, `0`, `""`, or empty collection. +## Truthiness (for ternary and Elvis) ---- +Only `true` is truthy. The following are treated as non-truthy: `false`, `unknown`, `null`, `0`, `""`, and empty collections. So `expr ?: default` yields `default` when `expr` is any of these. -## Examples in Action +## Short-circuit and evaluation order -### Combining conditions and using a default value +- **and:** Left-to-right. If the left operand is false, the right is not evaluated. +- **or:** Left-to-right. If the left operand is true, the right is not evaluated. +- **? ::** Only the branch selected by the condition is evaluated. The condition is always evaluated first. + +## Examples in Action -You are building a rule that depends on role and age, and you want a readable label or a fallback when a field is missing. +### Logical and comparison ```sentrie let a: bool = true and false let b: bool = age >= 18 let c: bool = not (user.role in allowed_roles) +``` + +### Ternary and Elvis + +```sentrie let d: string = age >= 18 ? "adult" : "minor" let e: string = user.name ?: "Anonymous" ``` -### Checking role with regex and negated membership +### Pattern matching (regex) -You need to allow access for admins or active users, validate an email format, and exclude guests. +```sentrie +let g: bool = email matches `^[a-z]+@[a-z]+\\.com$` +``` + +Left and right must be strings. The right is interpreted as a Go regexp. Invalid pattern causes an evaluation error. + +### Combining conditions ```sentrie let f: bool = user.role == "admin" or (user.role == "user" and user.status == "active") -let g: bool = email matches `^[a-z]+@[a-z]+\\.com$` let h: bool = not (role in ["guest"]) and status == "active" ``` ---- - ## Good to Know Before you wire these into policies, keep a few boundaries in mind: -- **not / !:** Unary prefix; one operand. It is converted to trinary then negated (true↔false; unknown stays unknown). `not unknown` yields `unknown`. You can write `not expr` or `! expr` (no space required after `!`). +- **not / !:** Unary prefix; one operand. Converted to trinary then negated. `not unknown` yields `unknown`. You can write `not expr` or `! expr` (no space required after `!`). - **Short-circuit:** `and` and `or` evaluate left-to-right; the right side may be skipped. The ternary evaluates only the chosen branch. - **Comparison:** Both sides must be comparable; result is trinary. Equality with `unknown` yields `unknown`. -- **matches:** Left and right must be strings. The right side is interpreted as a [Go regexp](https://pkg.go.dev/regexp) pattern. Invalid pattern string causes an evaluation error. For membership in collections or substrings, use [in](/reference/membership-operations) or [contains](/reference/membership-operations). +- **matches:** Left and right must be strings. Right is a [Go regexp](https://pkg.go.dev/regexp) pattern. Invalid pattern causes an evaluation error. For membership in collections or substring checks, use [in](/reference/membership-operations) or [contains](/reference/membership-operations). diff --git a/src/content/docs/reference/collection-operations.md b/src/content/docs/reference/collection-operations.md index 91e455a..f12e513 100644 --- a/src/content/docs/reference/collection-operations.md +++ b/src/content/docs/reference/collection-operations.md @@ -1,10 +1,9 @@ --- title: Collection Operations -description: "Quantifiers and transformers: any, all, filter, map, reduce, count, distinct." +description: Exhaustive reference for quantifiers and transformers: any, all, filter, map, reduce, count, distinct; syntax, parameters, and edge cases. --- - -Collection operations apply to lists and maps. They are declarative: they return new values or collections and do not mutate the input. Syntax uses a block with `yield`. +Collection operations apply to lists and maps. They are declarative: they return new values or new collections and do not mutate the input. Syntax uses a block with a single `yield` per iteration. The collection is iterated in order (list order or map iteration order); for `reduce`, an initial value is combined with each element via the yielded expression. ## Syntax @@ -18,25 +17,44 @@ count collection distinct collection ``` -Index parameter is optional in some forms. For maps, element is key-value or value depending on operation. +- **element, index:** Loop variables. For lists, element is the item and index is the position (0-based). For maps, element and index semantics are implementation-defined (e.g. key-value pair or value and key). The index parameter may be optional in some forms. +- **acc, element, index:** For `reduce`, `acc` is the accumulator (initial value on first iteration, then the previous `yield` result), `element` is the current element, and `index` is the position/key as above. ## Configuration & Arguments | Operation | Input | Output | Description | -| :--- | :--- | :--- | :--- | -| `any` | collection | bool/trinary | True if at least one element yields truthy. | -| `all` | collection | bool/trinary | True if all elements yield truthy. | -| `filter` | collection | same type | New collection of elements for which yield is truthy. | -| `map` | collection | list | New list of yield values. | -| `reduce` | collection, initial | type of initial | Fold: acc = initial, then acc = yield for each element. | -| `count` | collection | number | Number of elements. | -| `distinct` | collection | same type | New collection with duplicates removed. | - -**Returns:** As in table. Empty collection: `any` false, `all` true, `count` 0. Reduce with empty collection returns initial. +| :-------- | :---- | :----- | :---------- | +| `any` | collection | bool/trinary | true if at least one element yields a truthy value. Short-circuits: iteration stops at first truthy. Empty collection → false. | +| `all` | collection | bool/trinary | true if every element yields a truthy value. Short-circuits: iteration stops at first falsy. Empty collection → true. | +| `filter` | collection | same type | New collection containing only elements for which the block yields truthy. | +| `map` | collection | list | New list whose elements are the yielded values (one per element). Type of each yield can be any type. | +| `reduce` | collection, initial | type of initial | Fold: start with `acc = initial`; for each element, set `acc` to the yielded expression. Final `acc` is the result. Empty collection → returns initial. | +| `count` | collection | number | Number of elements in the collection. Empty → 0. | +| `distinct` | collection | same type | New collection with duplicate elements removed. Equality for deduplication is by the language’s `==`. | + +**Returns:** As in the table. For `any`/`all`, the result is the trinary/boolean produced by the predicate. For `filter`/`map`/`distinct`, the result is a new collection (or list for `map`). For `reduce`, the result is the final accumulator. For `count`, the result is a number. + +## Block and yield rules + +- **any, all, filter:** The block must yield a value that is interpreted as truthy or falsy (trinary/boolean). One yield per iteration. The block is evaluated once per element. +- **map:** The block must yield an expression per iteration. The type of the yielded value can be any type; the result is a list of those values. +- **reduce:** The block must yield an expression that becomes the next accumulator. First iteration: `acc` is `initial`. Subsequent iterations: `acc` is the previous yield. The final yield (after the last element) is the result of `reduce`. + +## Empty collection behavior + +| Operation | Empty collection result | +| :-------- | :---------------------- | +| `any` | false | +| `all` | true | +| `filter` | empty collection | +| `map` | empty list | +| `reduce` | initial (unchanged) | +| `count` | 0 | +| `distinct` | empty collection | ## Examples in Action -### Typical use +### any, all, filter, map, reduce, count, distinct ```sentrie let has_even: bool = any numbers as num, idx { yield num % 2 == 0 } @@ -48,7 +66,7 @@ let n: number = count numbers let uniq: list[number] = distinct numbers ``` -### Going further +### reduce with initial and multiple uses ```sentrie let sum: number = reduce scores from 0 as acc, score, idx { yield acc + score } @@ -59,9 +77,7 @@ let avg: number = sum / count scores Before you implement this, keep a few boundaries in mind: -- Only valid on collections (lists, maps). Original collection is not modified. -- Block must yield once per iteration. Type of yield must match (trinary for any/all/filter predicate; expr for map/reduce). - - -- Empty collection: `any` → false, `all` → true, `filter`/`map`/`distinct` → empty, `count` → 0, `reduce` → initial. -- Reduce: first iteration uses initial as acc; subsequent use previous yield as acc. +- **Applicability:** Only valid on collections (lists, maps). The original collection is not modified. +- **Block:** Must yield once per iteration. For `any`/`all`/`filter` the yield is a predicate (trinary/boolean); for `map`/`reduce` the yield is an expression that becomes the new value or accumulator. +- **Empty collection:** `any` → false, `all` → true, `filter`/`map`/`distinct` → empty result, `count` → 0, `reduce` → initial. +- **Reduce:** First iteration uses `initial` as `acc`; each subsequent iteration uses the previous `yield` result as `acc`. diff --git a/src/content/docs/reference/facts.md b/src/content/docs/reference/facts.md index 444762a..405a3b9 100644 --- a/src/content/docs/reference/facts.md +++ b/src/content/docs/reference/facts.md @@ -1,58 +1,80 @@ --- title: Facts -description: "Fact declaration syntax: required/optional, type, alias, default." +description: Exhaustive reference for fact declaration syntax: required/optional, type, alias, default, and binding at evaluation. --- -When you need to pass structured input into a policy (e.g. user, request, or config), you declare facts. Facts are named, typed inputs declared at the top of the policy. They are required by default; add `?` for optional. Only optional facts may have a default. Facts are non-nullable so evaluation stays predictable. +Facts are named, typed inputs to a [policy](/reference/policies). They are declared at the top of the policy (before [let](/reference/let), [use](/reference/functions), and [rules](/reference/rules)). Each fact has a name, a type (shape or primitive), an optional alias used in the policy body, and—only for optional facts—an optional default expression. Required facts must be supplied at evaluation time or evaluation fails. Facts are non-nullable when supplied: null is not allowed as a fact value. -Here is the basic syntax: +## Syntax ```text -fact name : type [ as alias ] [ default expr ] -- required +fact name : type [ as alias ] [ default expr ] -- required fact name? : type [ as alias ] [ default expr ] -- optional ``` +- **name / name?:** Identifier. Trailing `?` makes the fact optional; otherwise it is required. +- **type:** Any type: primitive (`number`, `string`, `trinary`, `bool`, `document`), collection (`list[T]`, `map[T]`, `record[...]`), or a [shape](/reference/shapes). The supplied value is validated against this type (and any constraints) at evaluation. +- **as alias:** Optional. Identifier used in the policy body to refer to this fact. If omitted, the body uses `name`. +- **default expr:** Optional. Allowed only for **optional** facts (`name?`). Used when the fact is omitted from the input. The expression is evaluated in the policy context (e.g. can reference other facts or policy-level let if order allows). + ## Configuration & Arguments -You can customize each fact using these parts: +| Part | Type | Required | Description | +| :--- | :--- | :------- | :---------- | +| `name` | identifier | Yes | Declaration name. Used in `with` bindings when another policy [imports](/language-concepts/policy-composition) this policy: the binding key must match the **alias** (or name if no `as`). | +| `name?` | - | No | `?` makes the fact optional. Optional facts may be omitted from the input; if omitted, `default expr` is used if present. | +| `type` | type ref | Yes | Type of the fact value. Can be a shape, primitive, or collection. Validation (and [constraints](/reference/constraints)) runs when the fact is bound. | +| `as alias` | identifier | No | Name used in the policy body. If absent, body uses `name`. Import `with` must use the alias (or name if no alias). | +| `default expr` | expression | No | Only for optional facts. Evaluated when the fact is omitted. Type should match the fact type (and constraints). | -| Argument | Type | Required | What it does | -| :------- | :--- | :------- | :----------- | -| `name` | identifier | Yes | Declaration name. | -| `name?` | - | No | `?` makes the fact optional. | -| `type` | shape/primitive | Yes | Type of the fact value. | -| `as alias` | identifier | No | Name used in the policy body; default is `name`. | -| `default expr` | expression | No | Only for optional facts; used when the fact is omitted. | +**Returns:** N/A (declaration). At evaluation time, the fact name (or alias) is bound to the provided value (or default). The policy body refers to the fact by alias or name. -**Returns:** N/A (declaration). At evaluation time, fact names (or aliases) are bound to the provided JSON input. +## Required vs optional ---- +- **Required (`fact name : type`):** The evaluator must receive a value for this fact. If it is missing, evaluation fails (e.g. `ErrRequiredFact`). The value must not be null and must conform to the declared type and constraints. +- **Optional (`fact name? : type`):** The evaluator may omit this fact. If omitted, the fact is bound to `default expr` if present; otherwise the behavior is tooling-defined (e.g. undefined or a type-specific default). If supplied, the value must be non-null and conform to the type and constraints. -## Examples in Action +## Import binding (policy composition) + +When another policy imports a decision from this policy (e.g. `import decision of RuleName from "ns" with ...`), the `with` clause binds values to the **target policy’s facts**. The key in `with` must be the **alias** of the fact (or the fact name if no `as` was used). The value must match the fact’s type and constraints. -### Declaring a required user fact and an optional context +## Examples in Action -You need a user shape for every evaluation and an optional context that defaults when not supplied. +### Required fact with alias ```sentrie fact user: User as currentUser +``` + +In the policy body, use `currentUser`. When importing this policy’s decision, bind with the key `currentUser` (e.g. `with currentUser = someValue`). + +### Optional fact with default + +```sentrie fact context?: Context as ctx default {} ``` -### Using primitive types and document with defaults +If the caller omits `ctx`, it is bound to `{}`. If provided, it must be non-null and of type `Context`. -You have a string fact with an alias and an optional document fact for config. +### Primitive and document facts ```sentrie fact userId: string as id fact config?: document as settings default { "env": "production" } ``` ---- +### Multiple facts, mix of required and optional + +```sentrie +fact user: User as currentUser +fact request: Request as req +fact options?: Options as opts default {} +``` ## Good to Know Before you implement this, keep a few boundaries in mind: -- **Constraint:** Facts must be declared before rules; only facts or comments may precede fact declarations. Required facts must be supplied at evaluation or evaluation fails. Optional facts may be omitted; if provided they must be non-null. Null is not allowed for any fact. Default is used when an optional fact is omitted. -- **Edge case:** Fact name in `with` for imports must match the **alias** (or name if no `as`) in the target policy. Type of the supplied value must match the fact type (and constraints) or evaluation fails. +- **Constraint:** Facts must be declared before rules; only facts or comments may precede fact declarations. Required facts must be supplied at evaluation or evaluation fails. Optional facts may be omitted; if supplied they must be non-null. Null is not allowed for any fact. Default is used when an optional fact is omitted. +- **Import:** When using [policy composition](/language-concepts/policy-composition), the fact name in the `with` clause must match the **alias** (or name if no `as`) in the target policy. The type of the supplied value must match the fact type (and constraints) or evaluation fails. +- **Order:** Fact declarations may reference only prior facts in their `default expr` (if the implementation allows it); typically defaults are literals or simple expressions. diff --git a/src/content/docs/reference/functions.md b/src/content/docs/reference/functions.md index 1a37fbd..fddd33e 100644 --- a/src/content/docs/reference/functions.md +++ b/src/content/docs/reference/functions.md @@ -1,42 +1,57 @@ --- title: Functions -description: Function call syntax and TypeScript module usage; memoization. +description: Exhaustive reference for function call syntax, TypeScript module import (use), aliasing, and memoization. --- - -Functions are called with `name(args...)` or `alias.name(args...)` for imported modules. Sentrie has no built-in global functions; all functions come from TypeScript modules imported with `use`. +Functions in Sentrie are provided by TypeScript modules. There are no built-in global functions in the language; all callable functions come from modules imported with `use` inside a [policy](/reference/policies). Calls use the form `functionName(args...)` for functions imported without an alias, or `alias.functionName(args...)` when the module is imported with an alias. ## Syntax -```text -functionName(arg1, arg2, ...) -alias.functionName(arg1, arg2, ...) -``` +**Call (direct):** `functionName(arg1, arg2, ...)` + +**Call (via alias):** `alias.functionName(arg1, arg2, ...)` + +**Import:** `use { fn1, fn2, ... } from source [ as alias ]` -Import: `use { fn1, fn2 } from source [ as alias ]` +- **source:** Either a built-in module reference (e.g. `@sentrie/hash`, no quotes) or a relative path string (e.g. `"./utils.ts"`, in quotes). +- **as alias:** Optional. Identifier used as the namespace for the imported functions. If omitted, the default alias is typically the last path segment of the source (e.g. `hash` for `@sentrie/hash`). ## Configuration & Arguments | Element | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| `source` | `@sentrie/module` or `"./file.ts"` | Yes | Built-in (no quotes) or relative path (quotes). | -| `as alias` | identifier | No | Default alias is last path segment (e.g. `hash` for `@sentrie/hash`). | -| args | expressions | Per function | Typed by the module; see [TypeScript modules](/reference/typescript_modules/). | +| :------ | :--- | :------- | :---------- | +| `source` | `@sentrie/module` or `"./path.ts"` | Yes | Built-in: `@sentrie/name` (no quotes). Relative: quoted string path from the policy file (or pack root). | +| `as alias` | identifier | No | Namespace for the imported functions. Omitted: default alias is last segment of source (e.g. `hash` for `@sentrie/hash`). | +| Function args | expressions | Per function | Typed and documented per module. See [Built-in TypeScript modules](/reference/typescript_modules/) and [Writing custom TypeScript modules](/extensibility/writing-custom-typescript-modules). | + +**Returns:** Per function; see the module’s documentation. Invalid arguments or runtime errors in the function abort evaluation. + +## Where use is allowed + +`use` is a policy-level statement. It must appear inside a [policy](/reference/policies) block, after [facts](/reference/facts) and before or alongside [let](/reference/let) and [rules](/reference/rules), per the policy statement order. The imported functions are visible to all rules and let bindings in that policy. They are not visible in other policies unless those policies declare their own `use`. + +## Resolution of source -**Returns:** Per function; see module docs. Invalid args or runtime errors abort evaluation. +- **Built-in:** `@sentrie/name` refers to a built-in module (e.g. `@sentrie/hash`, `@sentrie/time`). The runtime resolves these to the provided implementations. +- **Relative:** A string path (e.g. `"./utils.ts"`) is resolved relative to the policy file or the pack root, per tooling. Only paths that the [permissions](/reference/security-and-permissions) allow can be loaded. ## Examples in Action -### Typical use +### Import and call (no alias, default alias) ```sentrie use { sha256 } from @sentrie/hash -use { now } from @sentrie/time as time let h = sha256(data) +``` + +### Import with alias + +```sentrie +use { now } from @sentrie/time as time let t = time.now() ``` -### Going further +### Multiple functions and custom module ```sentrie use { calculateAge, validateEmail } from "./utils.ts" as utils @@ -47,8 +62,7 @@ yield utils.calculateAge(user.birthDate) >= 18 and utils.validateEmail(user.emai Before you implement this, keep a few boundaries in mind: -- Functions are memoized per (function, args) when applicable; repeated calls with same args may return cached result. -- Module scope: `use` is per policy; alias is used in that policy only. - - -- Missing or wrong-type arguments can cause runtime errors. See [Built-in TypeScript modules](/reference/typescript_modules/) and [Writing custom TypeScript modules](/extensibility/writing-custom-typescript-modules) for contracts. +- **Memoization:** Functions may be memoized per (function, arguments) when applicable; repeated calls with the same arguments may return a cached result. This is implementation-defined. +- **Scope:** `use` is per policy. The alias (or default) is used only in that policy. Other policies must declare their own `use` to call the same or other modules. +- **Errors:** Missing or wrong-type arguments, or runtime errors inside the function, can cause evaluation to abort. See [Built-in TypeScript modules](/reference/typescript_modules/) and [Writing custom TypeScript modules](/extensibility/writing-custom-typescript-modules) for function contracts and types. +- **Permissions:** Custom modules (e.g. `"./file.ts"`) are subject to [security and permissions](/reference/security-and-permissions) (e.g. filesystem read). Built-in modules run with the same permissions as the pack. diff --git a/src/content/docs/reference/index.md b/src/content/docs/reference/index.md index be58474..004cbf8 100644 --- a/src/content/docs/reference/index.md +++ b/src/content/docs/reference/index.md @@ -1,40 +1,62 @@ --- title: Policy Language Reference -description: Exhaustive dictionary of Sentrie language syntax and features. +description: Exhaustive dictionary of Sentrie language syntax, types, operators, and constructs. --- +This section is a strict reference for the Sentrie policy language. Every page is intended to be exhaustive: full syntax variants, all options and arguments in tables, when and where each feature applies, edge cases, and multiple examples. For conceptual overviews and “how it works,” see [Language Concepts](/language-concepts/type-system-shapes). -This section is a strict reference for the Sentrie policy language: syntax, types, operators, and constructs. For conceptual overviews, see [Language Concepts](/language-concepts/type-system-shapes). +## Program structure -## Syntax - -A program is one namespace per file, then top-level policies and shapes: +A program consists of one [namespace](/reference/namespaces) per file (the first statement), followed by top-level [policies](/reference/policies) and [shapes](/reference/shapes). Policies contain facts, let, use, rules, and exports. Shapes can be exported for use in other namespaces. ```text namespace FQN -policy IDENT { ... } -shape IDENT { ... } +policy IDENT { fact ... let ... use ... rule ... export decision of IDENT } +shape IDENT { ... } | shape IDENT baseType @constraint export shape IDENT ``` ## Reference Pages -**Core:** [Namespaces](/reference/namespaces) · [Policies](/reference/policies) · [Rules](/reference/rules) · [Facts](/reference/facts) · [Intermediate values (let)](/reference/let) +### Core -**Types:** [Types and values](/reference/types-and-values) · [Constraints](/reference/constraints) · [Trinary](/reference/trinary) · [Shapes](/reference/shapes) +- **[Namespaces](/reference/namespaces):** One namespace per file, FQN syntax, placement, visibility of shapes and policies, cross-namespace reference. +- **[Policies](/reference/policies):** Policy block, statement order (facts, let, use, rules, export), evaluation. +- **[Rules](/reference/rules):** Rule syntax, when/default/body, outcome type, truthiness, cross-references. +- **[Facts](/reference/facts):** Required/optional facts, type, alias, default, binding at evaluation, import binding. +- **[Intermediate values (let)](/reference/let):** let syntax, scoping, immutability, type validation. -**Operations:** [Arithmetic](/reference/arithmetic-operations) · [Boolean](/reference/boolean-operations) · [Collection operations](/reference/collection-operations) · [Functions](/reference/functions) · [Precedence](/reference/precedence) +### Types -**Other:** [Security and permissions](/reference/security-and-permissions) +- **[Types and values](/reference/types-and-values):** Primitives, collections, indexing, cast, validation. +- **[Constraints](/reference/constraints):** Constraint syntax, full tables per type (number, string, list, trinary), when checked, collection and cast behavior. +- **[Trinary](/reference/trinary):** true/false/unknown, truthiness, Kleene AND/OR/NOT, use in when and conditionals. +- **[Shapes](/reference/shapes):** Data models, field modifiers (required/optional, nullable), composition, type aliases, export. -**TypeScript:** [Built-in TypeScript modules](/reference/typescript_modules/) (overview and per-module pages) +### Operations -**Composition:** [Policy composition](/language-concepts/policy-composition) (export/import) · [Writing custom TypeScript modules](/extensibility/writing-custom-typescript-modules) +- **[Arithmetic](/reference/arithmetic-operations):** +, -, *, /, %, unary +/-, types, division by zero. +- **[Boolean](/reference/boolean-operations):** and, or, xor, not, comparison, matches, ternary, Elvis. +- **[Collection operations](/reference/collection-operations):** any, all, filter, map, reduce, count, distinct. +- **[Functions](/reference/functions):** Function calls, use (import), aliasing, memoization. +- **[Precedence](/reference/precedence):** Operator precedence table, associativity, parentheses. +- **[Membership](/reference/membership-operations):** in, contains (list, map, string). -## Good to Know +### Other + +- **[Security and permissions](/reference/security-and-permissions):** Pack permissions (fs_read, net, env), defaults, configuration. + +### TypeScript -Before you implement this, keep a few boundaries in mind: +- **[Built-in TypeScript modules](/reference/typescript_modules/):** Overview and per-module reference. + +### Composition and extensibility + +- **[Policy composition](/language-concepts/policy-composition):** Export/import of rules and facts. +- **[Writing custom TypeScript modules](/extensibility/writing-custom-typescript-modules):** Custom modules and use. + +## Good to Know - One namespace per file; namespace must be the first statement (comments allowed before). - Policies must export at least one rule to be executable or importable. -- All inputs and outputs in the reference are explicitly typed; edge cases are listed on each feature page. +- Each reference page lists syntax, configuration/arguments, examples, and edge cases; use them as the single source of truth for the language. diff --git a/src/content/docs/reference/let.md b/src/content/docs/reference/let.md index ec33518..5021c49 100644 --- a/src/content/docs/reference/let.md +++ b/src/content/docs/reference/let.md @@ -1,31 +1,45 @@ --- title: Intermediate Values (let) -description: let declaration syntax, scoping, and immutability. +description: Exhaustive reference for let declaration syntax, scoping, immutability, and type validation. --- +`let` binds a name to an expression inside a block. It is used for intermediate values in a [policy](/reference/policies) or inside a [rule](/reference/rules) body. The binding is scoped to the block, is immutable (no reassignment), and cannot be exported. Only [rules](/reference/rules) can be exported. If a type annotation is present, the value is validated at runtime (including [constraints](/reference/constraints)); validation failure aborts evaluation. -When you need to reuse a value or expression inside a policy or rule without repeating it, you use `let`. It binds a name to an expression inside a block, is scoped to that block, and is immutable. It cannot be exported—only used for intermediate calculations. - -Here is the basic syntax: +## Syntax ```text let name = expr let name : type = expr ``` +- **name:** Identifier. Must be unique within the same block (inner blocks can shadow outer names). +- **type:** Optional. Any type reference (primitive, collection, or [shape](/reference/shapes)), optionally with [constraints](/reference/constraints). When present, the value of `expr` is validated against this type at runtime. +- **expr:** Any expression. Evaluated once; the result is bound to `name`. + ## Configuration & Arguments | Part | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| `name` | identifier | Yes | Variable name. | -| `type` | type | No | Optional annotation; validates assignment. | -| `expr` | expression | Yes | Initial value. | +| :--- | :--- | :------- | :---------- | +| `name` | identifier | Yes | Variable name. Visible in the rest of the block (and inner blocks unless shadowed). | +| `type` | type ref | No | If present, the assigned value is validated against this type and any constraints. Failure aborts evaluation. | +| `expr` | expression | Yes | Initial value. Evaluated once at the point of the `let`. | + +**Returns:** N/A (binding). The name evaluates to the bound value wherever it is used in scope. + +## Scoping + +- **Policy-level let:** Declared in the policy block (after [facts](/reference/facts), alongside [use](/reference/functions)). Visible to all [rules](/reference/rules) in that policy. Not visible in other policies. +- **Rule-level let:** Declared inside a rule body (before the `yield`). Visible only within that rule body. Policy-level let is also visible inside the rule unless shadowed. +- **Shadowing:** A `let` in an inner block (e.g. inside a rule) can reuse the same name as an outer binding; the inner name shadows the outer one in the inner scope. -**Returns:** N/A (binding). The name evaluates to the bound value in scope. +## Immutability and export + +- **Immutability:** There is no syntax to reassign a `let` binding. The name always refers to the value computed at the `let` site. +- **Export:** Only rules can be exported. `let` bindings cannot be exported; they are for intermediate computation only. ## Examples in Action -### Binding a single intermediate value +### Untyped and typed let ```sentrie let adminRoles = ["admin", "super_admin"] @@ -33,22 +47,47 @@ let totalPrice = item.price * quantity let count: number = 10 ``` -### Chaining let for readability +Without a type, the value is not validated against a type. With a type, the value must conform (and satisfy constraints if any). + +### Typed let with constraints ```sentrie let count: number @min(0) @max(100) = 50 ``` -Block scope: `let` inside a rule body is only visible in that body; policy-level `let` is visible to all rules in the policy. +If `50` were outside `[0, 100]`, evaluation would abort. -## Good to Know +### Policy-level let used in rules -Before you implement this, keep a few boundaries in mind: +```sentrie +policy P { + fact user: User + let roles = ["admin", "editor"] + rule allow = default false { yield user.role in roles } + export decision of allow +} +``` + +`roles` is visible in the rule body. + +### Let inside a rule body -- Scoped to the immediate block (`{}`). Policy-level: visible to all rules in the policy. -- Immutable: cannot be reassigned. -- Cannot be exported; only rules can be exported. -- Type annotation is optional; if present, value is validated at runtime. +```sentrie +rule getPrice = default 0 when product.price is defined { + let base = product.price + let discount = user.isPremium ? 0.1 : 0.05 + yield base * (1 - discount) +} +``` +`base` and `discount` are visible only until the `yield`. + +## Good to Know + +Before you implement this, keep a few boundaries in mind: -- Same name in inner block shadows outer. Constraint validation failure aborts evaluation. +- **Scope:** Let is scoped to the immediate block. Policy-level let is visible to all rules in the policy. Rule-level let is visible only in that rule body. Same name in an inner block shadows the outer binding. +- **Immutability:** Let bindings cannot be reassigned. The name always refers to the initial value. +- **Export:** Only rules can be exported. Let cannot be exported. +- **Type annotation:** Optional. If present, the value is validated at runtime against the type and any constraints; constraint or type failure aborts evaluation. +- **Shadowing:** Inner blocks can declare the same name as an outer let; the inner name shadows the outer in the inner scope. diff --git a/src/content/docs/reference/membership-operations.md b/src/content/docs/reference/membership-operations.md index 4fef940..8033874 100644 --- a/src/content/docs/reference/membership-operations.md +++ b/src/content/docs/reference/membership-operations.md @@ -1,43 +1,54 @@ --- title: Membership Operations -description: "Containment operators: in and contains (list, map, string)." +description: Exhaustive reference for containment operators: in and contains (list, map, string); semantics and edge cases. --- -When you need to check whether a value appears in a list, a key exists in a map, or a substring appears in a string, you use `in` or `contains`. Both return a boolean. `needle in haystack` and `haystack contains needle` are equivalent—pick the one that reads better in your policy. +The `in` and `contains` operators test containment: whether a value appears in a list, a key exists in a map, a substring appears in a string, or (for maps) one map is a “subset” of another. Both return a boolean. `needle in haystack` and `haystack contains needle` are equivalent; only the argument order differs. -Here is the basic syntax: +## Syntax ```text needle in haystack haystack contains needle ``` -**in:** Left = needle, right = haystack. True if haystack contains needle. **contains:** Left = haystack, right = needle. Same result; argument order reversed. +- **in:** Left operand = needle (the value to find), right operand = haystack (the container). +- **contains:** Left operand = haystack, right operand = needle. Same result as `in` with arguments swapped. ## Configuration & Arguments -You can test containment with these operators: - -| Operator | Left | Right | What it does | -| :------- | :--- | :---- | :----------- | -| `in` | needle | haystack | True if haystack (string, list, or map) contains needle. | +| Operator | Left | Right | Result | +| :------- | :--- | :---- | :----- | +| `in` | needle | haystack | true if haystack contains needle; otherwise false. | | `contains` | haystack | needle | Same as above; argument order reversed. | -**String:** Both sides strings. True if needle is a non-empty substring of haystack. +**Returns:** bool. Not trinary. Type mismatch or unsupported combination yields false (or key-mismatch for map-subset case). -**List:** Haystack is a list; needle is any value. True if any element equals needle (by `==`). +## Semantics by type -**Map:** Haystack is a map. If needle is a string, true if that key exists. If needle is a map, true if haystack has all keys from needle with the same values. +### String -**Returns:** bool. Invalid or unsupported types yield false (or key-mismatch for map subset). No trinary. +- **Haystack:** string. **Needle:** string. +- **Result:** true if needle is a **non-empty** substring of haystack; false if needle is empty, or not a substring, or types are wrong. ---- +### List -## Examples in Action +- **Haystack:** list. **Needle:** any value. +- **Result:** true if any element of the list is equal to needle (by `==`). Element-wise equality; no deep structural comparison beyond the language’s equality. Empty list → false. -### Checking substring and list membership +### Map — key presence -You want to see if a role string is in a list of roles, or if a substring appears in a longer string. +- **Haystack:** map. **Needle:** string. +- **Result:** true if the map has a key equal to needle; false otherwise. Only key presence is checked; the value is irrelevant. + +### Map — subset + +- **Haystack:** map. **Needle:** map. +- **Result:** true if every key in needle exists in haystack and the corresponding values are equal (by `==`). Extra keys in haystack are allowed. If any key in needle is missing from haystack or any value differs, result is false. + +## Examples in Action + +### Substring and list membership ```sentrie let in_str: bool = "foo" in "xfoobar" @@ -45,21 +56,19 @@ let has_role: bool = "admin" in user.roles let has_guest: bool = roles contains "guest" ``` -### Checking map key presence and subset - -You need to know if a key exists on a map, or if one map is a “subset” of another (all keys present with matching values). +### Map key presence and subset ```sentrie -let sub: bool = "admin" in roles let key_exists: bool = "plan" in subscription let subset: bool = {"a": 1, "b": 2} contains subscription ``` ---- - ## Good to Know Before you implement this, keep a few boundaries in mind: -- **Constraint:** For strings, needle must be non-empty for a true result; empty needle → false. For lists, element-wise equality; no deep comparison beyond `equals`. For maps, needle string checks key presence; needle map requires every key in needle to exist in haystack with the same value; extra keys in haystack are allowed. -- **Edge case:** Type mismatch (e.g. haystack not string/list/map, or needle wrong type for map) → false. Empty list or empty string haystack → false. For regex matching on strings, use [matches](/reference/boolean-operations) in Boolean Operations. +- **Strings:** Needle must be non-empty for a true result; empty needle → false. Empty string haystack → false (unless needle is also empty; then typically false by “non-empty substring” rule). +- **Lists:** Element-wise equality; no deep comparison beyond the language’s `==`. Empty list → false. +- **Maps (key):** Needle string checks key presence only. Map keys are strings. +- **Maps (subset):** Needle map: every key in needle must exist in haystack with the same value; extra keys in haystack are allowed. Type mismatch (e.g. haystack not a map, or needle not string/map) → false. +- **Regex:** For pattern matching on strings, use [matches](/reference/boolean-operations) in Boolean Operations. diff --git a/src/content/docs/reference/namespaces.md b/src/content/docs/reference/namespaces.md index 013d95b..f6cf888 100644 --- a/src/content/docs/reference/namespaces.md +++ b/src/content/docs/reference/namespaces.md @@ -1,54 +1,80 @@ --- title: Namespaces -description: Namespace syntax and rules for organizing policies and shapes. +description: Exhaustive reference for namespace syntax, placement rules, visibility of shapes and policies, and cross-file behavior. --- -When you need to group policies and shapes and control which shapes are visible where, you put them under a namespace. Each `*.sentrie` file has exactly one namespace, and it must be the first statement. The namespace is the visibility boundary: unexported shapes are visible only within the same namespace. +Namespaces group [policies](/reference/policies) and [shapes](/reference/shapes) and define visibility boundaries. Each `*.sentrie` file has exactly one namespace declaration. The namespace must be the first statement in the file (only comments may appear before it). Unexported shapes are visible only within the same namespace; exported shapes can be referenced from other namespaces. -Here is the basic syntax: +## Syntax ```text namespace fully/qualified/name ``` -The name is slash-separated identifiers (e.g. `com/example/auth`, `mycompany/policies/security`). +The name is a **fully qualified name (FQN)**: one or more identifiers separated by forward slashes (e.g. `com/example/auth`, `mycompany/policies/security`, `app/v1`). No leading or trailing slash. Each segment must be a valid identifier. ## Configuration & Arguments -You declare a namespace with a single path: +| Element | Type | Required | Description | +| :------ | :--- | :------- | :---------- | +| FQN | identifier path | Yes | Slash-separated identifiers. Forms the namespace for every declaration (policies, shapes) in this file. | -| Argument | Type | Required | What it does | -| :------- | :--- | :------- | :----------- | -| FQN | identifier path | Yes | Slash-separated identifiers; forms the namespace for that file. | +**Returns:** N/A (declaration). The namespace does not produce a value; it attaches a scope to the declarations that follow. -**Returns:** N/A (declaration). +## Placement and ordering rules ---- +- **First statement:** The namespace declaration must be the first statement in the file. Only comments (line or block) may appear before it. +- **One per file:** Exactly one namespace per `*.sentrie` file. Multiple files in a pack may declare different namespaces; the pack can therefore contain multiple root namespaces. +- **No nesting syntax:** The language does not have separate “nested” namespace blocks. Hierarchy is by naming convention: e.g. `a/b/c` is a single FQN; there is no separate declaration for `a` or `a/b`. + +## Visibility and cross-namespace reference + +- **Policies and shapes** declared in a file belong to that file’s namespace. They are visible by simple name within the same namespace (same file or other files in the same pack that share the same namespace, if the tooling supports multiple files per namespace). +- **Exported shapes:** A shape can be exported with `export shape Name`. Exported shapes are visible to other namespaces for use as types (e.g. fact types, [composition](/reference/shapes) base). See [Policy composition](/language-concepts/policy-composition) and [Shapes](/reference/shapes) for how other namespaces refer to them. +- **Exported rules:** Rules are exported via `export decision of RuleName` inside a policy. Other namespaces can import and call those decisions; the namespace and policy identify the source. ## Examples in Action -### Organizing policies by product or team +### Single-segment and multi-segment namespaces + +```text +namespace auth +namespace com/example/auth +namespace mycompany/policies/billing/v2 +``` -You want a clear hierarchy so policies for auth, billing, or privacy live under different path segments. +### Comments before namespace (allowed) ```text +// This file defines the auth policies. namespace com/example/auth -namespace com/example/billing/v2 + +policy allow { ... } ``` -### Using a deep path for a specific feature +### Multiple namespaces in a pack (different files) -You are grouping policies for a narrow feature (e.g. GDPR) under a long path. +File `auth.sentrie`: ```text -namespace mycompany/policies/privacy/gdpr +namespace com/example/auth +policy P { ... } ``` ---- +File `billing.sentrie`: + +```text +namespace com/example/billing +policy Q { ... } +``` + +The pack contains two namespaces. Import and resolution rules determine how one namespace references policies or exported shapes in another. ## Good to Know Before you implement this, keep a few boundaries in mind: -- **Constraint:** Only comments may appear before the namespace declaration. One namespace per file. Multiple root namespaces are allowed across a pack (in different files). -- **Edge case:** Namespace names must be valid identifiers. Child “namespaces” are not separate declarations; hierarchy is by naming convention (e.g. `a/b/c`). No leading or trailing slash. +- **Constraint:** Only comments may appear before the namespace declaration. One namespace per file. Multiple root namespaces are allowed across the pack (in different files). +- **Naming:** Each segment of the FQN must be a valid identifier. No leading or trailing slash. Child “namespaces” (e.g. `a` vs `a/b`) are not separate language constructs; hierarchy is by convention in the FQN string. +- **Resolution:** When another namespace references an exported shape or imports a decision, the full namespace path (and policy name, for rules) is used by the runtime/tooling to resolve the target. See [Policy composition](/language-concepts/policy-composition) for import syntax and behavior. +- **Files and namespaces:** The relationship between physical files and namespace FQNs is tooling-dependent; typically one file per namespace, but the reference does not forbid multiple files sharing the same namespace if the tooling supports it. diff --git a/src/content/docs/reference/policies.md b/src/content/docs/reference/policies.md index f219698..7578ae9 100644 --- a/src/content/docs/reference/policies.md +++ b/src/content/docs/reference/policies.md @@ -1,11 +1,11 @@ --- title: Policies -description: "Policy syntax and allowed statements: facts, rules, let, use, export." +description: Exhaustive reference for policy syntax, statement order (facts, let, use, rules, export), and evaluation behavior. --- -When you need to define a named set of inputs (facts), decision logic (rules), and optional helpers (`let`, `use`), you put them in a policy. A policy is a named block inside a namespace. It must have at least one rule and at least one `export decision of` so the policy can be run from the CLI or HTTP API. +A policy is a named block inside a [namespace](/reference/namespaces) that groups [facts](/reference/facts), optional [let](/reference/let) bindings, optional [use](/reference/functions) imports, [rules](/reference/rules), and one or more `export decision of` declarations. It defines the inputs and decision logic that the CLI or HTTP API can execute. To be runnable or importable, a policy must have at least one rule and at least one exported decision. -Here is the basic syntax: +## Syntax ```text policy IDENT { @@ -17,41 +17,43 @@ policy IDENT { } ``` +- **IDENT:** Policy name (identifier). Must be unique within the namespace (or follow tooling rules for overloads, if any). +- **Body:** Statements in a fixed order (see Configuration & Arguments). Facts first (if any), then let, use, rules, and finally export declarations. + ## Configuration & Arguments -You can structure a policy using these statements: +| Statement | Required | Order | Description | +| :-------- | :------- | :---- | :---------- | +| `fact` | No | First | Declare input facts. All fact declarations must appear before any `let`, `use`, or `rule`. Only facts (and comments) may precede facts. | +| `let` | No | After facts | Intermediate values. Scoped to the policy block; visible to all rules in the policy. | +| `use` | No | After let | Import TypeScript modules (e.g. `use { sha256 } from @sentrie/hash`). See [Functions](/reference/functions). | +| `rule` | Yes (≥1) | After use | Decision logic. At least one rule must be declared. | +| `export decision of` | Yes (≥1) | After rules | Expose a rule for execution (CLI/API) or for [import](/language-concepts/policy-composition) from other policies. The identifier after `of` must be the name of a rule in the same policy. | -| Argument | Required | What it does | -| :------- | :------- | :----------- | -| `fact` | No | Inputs; must appear before rules if present. | -| `let` | No | Intermediate values; scoped to the block. | -| `use` | No | Import TypeScript functions. | -| `rule` | Yes (≥1) | Decision logic. | -| `export decision of` | Yes (≥1) | Exposes a rule for execution or import. | +**Returns:** N/A (container). Evaluation returns the result of the invoked rule (the one targeted by the CLI, API, or import). The policy itself does not “return” a value; the exported rule does. -**Returns:** N/A (container). Evaluation returns the exported rule decision(s). +## Statement order and constraints ---- +- **Facts first:** If the policy has any `fact` declarations, they must come first. Only comments may appear before the first fact. No `let`, `use`, or `rule` may appear before a fact when facts exist. +- **Let and use:** After all facts, any `let` and `use` statements. Order between multiple `let` or multiple `use` is significant only where one binding references another (e.g. a later `let` may refer to an earlier `let` or to a fact). +- **Rules:** All rules follow. Rules can reference facts (by name or alias), policy-level `let` bindings, and other rules in the same policy by name. They cannot reference rules in other policies without import. +- **Export:** Each `export decision of RuleName` exposes that rule. The same rule may be exported once. The CLI and API select which exported rule to run (e.g. by name). For `import decision of` from another policy, the binding (e.g. `with` facts) uses the rule name as the target. ## Examples in Action -### Defining a simple allow/deny policy - -You have one fact (e.g. user) and one rule that yields true or false; you export that rule so `sentrie exec` or the API can call it. +### Minimal policy (one fact, one rule, one export) ```sentrie namespace com/example/auth policy userAccess { fact user: User as currentUser - rule allow = default false { yield user.role == "admin" } + rule allow = default false { yield currentUser.role == "admin" } export decision of allow } ``` -### Using TypeScript modules, optional facts, and let - -You need a hash function, an optional context fact with a default, and a list of roles used in the rule. +### Multiple facts, optional fact with default, let, and use ```sentrie policy userAccess { @@ -59,18 +61,35 @@ policy userAccess { fact user: User as currentUser fact context?: Context as ctx default {} let adminRoles = ["admin", "super_admin"] - rule canRead = default false when user.role is defined { - yield user.role in adminRoles + rule canRead = default false when currentUser.role is defined { + yield currentUser.role in adminRoles + } + rule canWrite = default false when currentUser.role is defined { + yield currentUser.role == "admin" } export decision of canRead + export decision of canWrite } ``` ---- +### Rules referencing other rules + +Rules in the same policy can call each other by name (no import needed): + +```sentrie +policy P { + fact user: User + rule isAdmin = default false { yield user.role == "admin" } + rule canEdit = default false { yield isAdmin } + export decision of canEdit +} +``` ## Good to Know Before you implement this, keep a few boundaries in mind: -- **Constraint:** Facts must be declared before rules (only facts or comments may precede facts). At least one rule must be exported for the policy to be executable or importable. Rules in the same policy may reference each other without import. -- **Edge case:** Policy name is an identifier. The same namespace may contain multiple policies. Exported rule names are the target for the CLI/API and for `import decision of` from other policies. +- **Constraint:** Facts must be declared before rules; only facts or comments may precede fact declarations. At least one rule must be exported for the policy to be executable or importable. Rules in the same policy may reference each other by name without import. +- **Policy name:** The policy name is an identifier. The same namespace may contain multiple policies. Each has its own facts, let, use, and rules. +- **Export target:** Exported rule names are the targets for the CLI/API and for `import decision of` from other policies. When importing, the binding (e.g. `with` clause) uses the rule name as defined in the target policy (and fact names/aliases as in that policy). +- **Evaluation:** When a decision is requested, the runtime evaluates the chosen rule (with facts bound). Only that rule’s body or default is run; other rules are not evaluated unless the chosen rule references them. diff --git a/src/content/docs/reference/precedence.md b/src/content/docs/reference/precedence.md index f8901d8..26484a4 100644 --- a/src/content/docs/reference/precedence.md +++ b/src/content/docs/reference/precedence.md @@ -1,54 +1,75 @@ --- title: Precedence -description: Operator precedence (highest to lowest) for expressions. +description: Exhaustive reference for operator precedence (highest to lowest), associativity, and use of parentheses. --- - -Operators are evaluated in order of precedence (highest first). Same precedence is left-to-right unless stated otherwise. Use parentheses to override. +Operators are evaluated in order of precedence: higher precedence binds first. When two operators have the same precedence, they are usually evaluated left-to-right; the ternary operator `? :` is right-associative. Parentheses `( ... )` override precedence and explicitly group subexpressions. ## Syntax -Expressions combine operators; precedence determines binding. Ternary `? :` is right-associative. +Expressions combine operators; precedence determines how the expression is grouped when parentheses are not used. There is no separate “precedence” keyword; the table below defines the ordering. + +## Configuration & Arguments (precedence table) + +| Level | Precedence | Operators | Description | +| :---- | :--------- | :-------- | :---------- | +| 1 | Highest | `( )`, `[ ]`, `.` | Primary: function call `f(...)`, indexing `e[i]`, member access `e.f`. Literals and identifiers are atoms. | +| 2 | | `not`, `!`, unary `+`, unary `-` | Unary: logical not, unary plus/minus. | +| 3 | | `*`, `/`, `%` | Multiplicative. | +| 4 | | `+`, `-` | Additive (binary). | +| 5 | | `<`, `<=`, `>`, `>=`, `in`, `matches`, `contains` | Comparison and containment. | +| 6 | | `==`, `!=`, `is`, `is not` | Equality. | +| 7 | | `and` | Logical AND. | +| 8 | | `xor` | Logical XOR. | +| 9 | | `or` | Logical OR. | +| 10 | Lowest | `? :` | Ternary conditional. **Right-associative.** | + +**Returns:** N/A (ordering rule). The result type is the type of the top-level expression after all operators are applied. -## Configuration & Arguments +## Associativity -| Precedence | Operators | Description | -| :--- | :--- | :--- | -| 1 | `()`, `[]`, `.` | Primary (literals, identifiers, calls, indexing) | -| 2 | `not`, `!`, unary `+`, `-` | Unary | -| 3 | `*`, `/`, `%` | Multiplicative | -| 4 | `+`, `-` | Additive | -| 5 | `<`, `<=`, `>`, `>=`, `in`, `matches`, `contains` | Comparison | -| 6 | `==`, `!=`, `is`, `is not` | Equality | -| 7 | `and` | Logical AND | -| 8 | `xor` | Logical XOR | -| 9 | `or` | Logical OR | -| 10 | `? :` | Ternary conditional | +- **Left-to-right:** Same-precedence operators (e.g. `+` and `-`, `*` and `/`, `and`, `or`, `xor`) group from the left unless overridden by parentheses. Example: `a - b - c` is `(a - b) - c`. +- **Right-associative:** The ternary `? :` groups from the right. Example: `a ? b : c ? d : e` is `a ? b : (c ? d : e)`. -**Returns:** N/A (ordering rule). Result type is that of the top-level expression. +## Parentheses + +Parentheses override precedence. Any subexpression can be wrapped in `( ... )` to force that subexpression to be evaluated first. Use parentheses when the intended grouping is not obvious. ## Examples in Action -### Typical use +### Precedence in arithmetic ```sentrie -let result: number = 2 + 3 * 4 -- 14 -let valid: bool = 5 + 3 > 7 -- true -let complex: bool = true and false or true -- true +let result: number = 2 + 3 * 4 -- 14 (multiplication before addition) +let valid: bool = 5 + 3 > 7 -- true (arithmetic before comparison) ``` -### Going further +### Logical precedence ```sentrie -let value: number = 5 > 3 ? 10 : 20 -- 10 -let nested: string = true ? (false ? "A" : "B") : "C" -- "B" +let complex: bool = true and false or true -- true (and before or; (true and false) or true) ``` -## Good to Know +### Ternary right-associativity -Before you implement this, keep a few boundaries in mind: +```sentrie +let value: number = 5 > 3 ? 10 : 20 -- 10 +let nested: string = true ? (false ? "A" : "B") : "C" -- "B" +``` + +Without parentheses, `a ? b : c ? d : e` is parsed as `a ? b : (c ? d : e)`. + +### Using parentheses to clarify + +```sentrie +let safe: bool = (user.role == "admin") and (age >= 18) +let sum: number = (a + b) * (c + d) +``` -- Parentheses override precedence. Same level: left to right except ternary (right-associative). +## Good to Know +Before you implement this, keep a few boundaries in mind: -- Use parentheses when in doubt to make intent explicit. +- **Parentheses:** Override precedence and associativity. Use them when in doubt to make intent explicit. +- **Same level:** Left to right except for ternary (`? :`), which is right-associative. +- **Primary:** Function calls, indexing, and dot access have the highest precedence so that `a.f()`, `a[i]`, and `a.b` are grouped as expected. diff --git a/src/content/docs/reference/rules.md b/src/content/docs/reference/rules.md index 843c8a5..020a8e4 100644 --- a/src/content/docs/reference/rules.md +++ b/src/content/docs/reference/rules.md @@ -1,12 +1,9 @@ --- title: Rules -description: Rule syntax, evaluation (when/default/body), and outcome (trinary or value). +description: Exhaustive reference for rule syntax, when/default/body evaluation, outcome type, and cross-references. --- - -When you need to define a single decision (e.g. allow/deny or a computed value) that can depend on a condition and a fallback, you write a rule. A rule has an optional `when` guard, an optional `default`, and a body that must contain `yield`. If `when` is truthy the body is evaluated; otherwise the `default` (or `unknown`) is used. - -Here is the basic syntax: +A rule defines a single named decision: an optional `when` guard, an optional `default` value when the guard is not truthy, and a body that must contain exactly one `yield`. If the `when` expression is truthy, the body is evaluated and its `yield` value is the rule’s outcome; otherwise the outcome is the `default` (or `unknown` if no default is given). Rules are the only construct that can be exported for execution or import. ## Syntax @@ -14,26 +11,49 @@ Here is the basic syntax: rule IDENT = [ default expr ] [ when expr ] { stmt* yield expr } ``` +- **IDENT:** Rule name (identifier). Must be unique within the [policy](/reference/policies). Used when exporting (`export decision of IDENT`) and when other rules in the same policy reference it. +- **default expr:** Optional. Evaluated only when `when` is not truthy; result becomes the rule outcome. If omitted and `when` is not truthy, outcome is `unknown`. +- **when expr:** Optional. Guard; must evaluate to a [trinary](/reference/trinary) or value treated as truthy/falsy. If omitted, treated as truthy (body always runs). If present and not truthy, body is skipped and `default` (or `unknown`) is used. +- **Body:** Block of statements ending with exactly one `yield expr`. When the body runs, it must reach that `yield`; the expression’s value is the rule outcome. + ## Configuration & Arguments | Part | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| `default expr` | expression | No | Used when `when` is not truthy; default is `unknown` if omitted. | -| `when expr` | trinary | No | Guard; if not truthy, rule outcome is `default`. Default when omitted is true. | -| body | block | Yes | Must contain exactly one `yield expr`. | +| :--- | :--- | :------- | :---------- | +| `default expr` | expression | No | Fallback outcome when `when` is not truthy. Type can be any value (e.g. bool, number, string). If omitted, non-truthy `when` yields `unknown`. | +| `when expr` | expression (trinary/truthy) | No | Guard. Evaluated first. Only if [truthy](/reference/trinary) (i.e. `true` in trinary semantics) is the body evaluated. Default when omitted: treated as true. | +| body | block | Yes | Statements (e.g. `let`) followed by exactly one `yield expr`. Only the chosen branch (body or default) is evaluated; the other is not. | -**Returns:** The `yield` expression value when body runs, else `default` (or `unknown`). Type is trinary or any value type. +**Returns:** When the body runs: the value of `yield expr`. When the guard is not truthy: the value of `default expr`, or `unknown` if there is no default. The rule’s result type is therefore the type of the yielded expression or the default expression (or trinary when outcome is `unknown`). + +## Evaluation semantics + +1. **Evaluate `when`** (if present). If absent, proceed as if truthy. +2. **Truthiness:** Only `true` is truthy. `false` and `unknown` are non-truthy. So `when user.role is defined` is truthy only when the expression evaluates to `true`. +3. **If truthy:** Evaluate the body. The body must contain exactly one `yield expr`; that expression’s value is the rule outcome. Any `let` or other statements in the body are evaluated in order before the `yield`. +4. **If not truthy:** Do not evaluate the body. The outcome is `default expr` if present, otherwise `unknown`. ## Examples in Action -### Defining a single rule with a default +### Rule with default only (no when) ```sentrie rule allow = default false { yield true } -rule isAdmin = default false when user.role is defined { yield user.role == "admin" } ``` -### Using when and TypeScript in a rule +Guard is effectively true; body runs and yields `true`. + +### Rule with when and default + +```sentrie +rule isAdmin = default false when user.role is defined { + yield user.role == "admin" +} +``` + +If `user.role` is not defined (or expression is false/unknown), outcome is `false`. Otherwise outcome is the result of `user.role == "admin"`. + +### Rule yielding a value (not just bool) ```sentrie rule getPrice = default 0 when product.price is defined { @@ -43,14 +63,24 @@ rule getPrice = default 0 when product.price is defined { } ``` -## Good to Know +Outcome type is number. Default `0` is used when `product.price` is not defined or when the guard is otherwise non-truthy. -Before you implement this, keep a few boundaries in mind: +### Rule referencing another rule in the same policy -- Evaluation: `is_truthy(when) ? body_result : default`. Truthy follows [trinary](/reference/trinary) semantics. -- Body must yield exactly once when evaluated. Only the chosen branch (body or default) is evaluated. -- Rules in the same policy can reference other rules by name. Exported rules can be imported elsewhere. +```sentrie +rule isAdmin = default false when user.role is defined { yield user.role == "admin" } +rule canEdit = default false { yield isAdmin } +export decision of canEdit +``` + +`canEdit`’s body evaluates `isAdmin`, which runs with the same facts. So the outcome of `canEdit` is the outcome of `isAdmin` in this example. +## Good to Know + +Before you implement this, keep a few boundaries in mind: -- If no `default` and `when` is not truthy, outcome is `unknown`. -- Recursion in rule evaluation is bounded by the language (no unbounded recursion). +- **Evaluation:** Semantics are `is_truthy(when) ? body_result : default`. Truthiness follows [trinary](/reference/trinary) semantics: only `true` is truthy. +- **Body:** Must contain exactly one `yield expr` when the body is evaluated. Only the chosen branch (body or default) is evaluated; the other is not. +- **Rules in same policy:** Can reference other rules by name (e.g. `yield isAdmin`). No import needed. Exported rules can be invoked from the CLI/API or [imported](/language-concepts/policy-composition) from other policies. +- **No default and non-truthy when:** If there is no `default` and `when` is not truthy, the outcome is `unknown`. +- **Recursion:** Rule evaluation is bounded by the language; unbounded recursion is not allowed. diff --git a/src/content/docs/reference/security-and-permissions.md b/src/content/docs/reference/security-and-permissions.md index 7ff53c9..4292723 100644 --- a/src/content/docs/reference/security-and-permissions.md +++ b/src/content/docs/reference/security-and-permissions.md @@ -1,54 +1,73 @@ --- title: Security and Permissions -description: "Policy pack permissions: filesystem, network, environment." +description: Exhaustive reference for policy pack permissions: filesystem read, network, and environment variables; configuration and defaults. --- - -Permissions define what a policy pack can access: filesystem (read), network hosts, and environment variables. They are set in `sentrie.pack.toml` under `[permissions]`. Default is pack-root filesystem only; no network; no env. +Permissions define what a policy pack can access at runtime: which filesystem paths can be read, which network hosts can be contacted, and which environment variable names are exposed to the pack. They are configured in the pack configuration file (e.g. `sentrie.pack.toml`) under a `[permissions]` section. Default behavior is restrictive: typically only the pack root is readable; no network access; no environment variables. Explicit entries grant access only to the listed paths, hosts, and variable names. ## Syntax +In the pack configuration file (e.g. `sentrie.pack.toml`): + ```text [permissions] -fs_read = ["/path1", "/path2"] -net = ["host1.com", "host2.com"] -env = ["VAR1", "VAR2"] +fs_read = ["path1", "path2", ...] +net = ["host1", "host2", ...] +env = ["VAR1", "VAR2", ...] ``` +- **fs_read:** List of paths (or path prefixes) that the pack is allowed to read. Paths can be absolute or relative to the pack root, per tooling. +- **net:** List of hosts (or patterns) allowed for outbound network access. Format is tooling-dependent (e.g. hostname, host:port, or pattern). +- **env:** List of environment variable names that are exposed to the pack. Only these names are visible; other environment variables are not. + ## Configuration & Arguments -| Key | Type | Description | -| :--- | :--- | :--- | -| `fs_read` | list[string] | Paths (or prefixes) that the pack can read. Default: pack root. | -| `net` | list[string] | Hosts (or patterns) allowed for network access. Default: none. | -| `env` | list[string] | Environment variable names exposed to the pack. Default: none. | +| Key | Type | Required | Description | +| :--- | :--- | :------- | :---------- | +| `fs_read` | list of strings | No | Paths (or prefixes) the pack can read. Default: typically pack root only (e.g. `["."]` or the pack directory). Paths outside this set cause a runtime error if access is attempted. | +| `net` | list of strings | No | Hosts (or patterns) allowed for network access. Default: none. Attempts to contact hosts not in the list (or not matching a pattern) fail at runtime. | +| `env` | list of strings | No | Environment variable names exposed to the pack. Default: none. Only these names are visible; other env vars are hidden. | + +**Returns:** N/A (configuration). Violations (e.g. reading a file outside `fs_read`, contacting a host not in `net`, or reading an env var not in `env`) cause runtime failure or denial. -**Returns:** N/A (configuration). Violations (e.g. reading outside fs_read) cause runtime failure. +## Default behavior + +- **Filesystem:** If `fs_read` is omitted or empty, the default is typically the pack root only. The pack can read files under that root (e.g. `.sentrie` files, local TypeScript modules). Reading outside the allowed set fails. +- **Network:** If `net` is omitted or empty, no network access is granted. TypeScript modules or built-ins that perform network I/O will fail unless the required host is listed. +- **Environment:** If `env` is omitted or empty, no environment variables are exposed. The pack cannot read `process.env` (or equivalent) except for the names explicitly listed. ## Examples in Action -### Typical use +### Minimal (pack root only) ```toml [permissions] fs_read = ["."] ``` -### Going further +### Explicit paths and network ```toml [permissions] -fs_read = ["/etc/passwd"] -net = ["example.com"] -env = ["ORG_DSN", "REDIS_PASSWORD"] +fs_read = [".", "/etc/app/config"] +net = ["api.example.com", "cdn.example.com"] ``` -## Good to Know +### Exposing environment variables -Before you implement this, keep a few boundaries in mind: +```toml +[permissions] +fs_read = ["."] +env = ["ORG_DSN", "REDIS_PASSWORD", "API_KEY"] +``` -- By default: filesystem access is limited to the policy pack root; no network; no environment variables. -- Explicit entries grant access only to listed paths/hosts/vars. Modules run with the same permissions as the pack. +Only these variable names are visible to the pack; others are not exposed. +## Good to Know + +Before you implement this, keep a few boundaries in mind: -- Invalid or missing paths/hosts may be rejected at load or runtime. Restrict permissions to the minimum required. +- **Default:** Filesystem access is typically limited to the policy pack root; no network; no environment variables. Explicit entries grant access only to the listed paths, hosts, and variable names. +- **Scope:** Permissions apply to the entire pack. All policies and TypeScript modules in the pack run with the same permissions. There is no per-policy or per-module permission granularity in this model. +- **Validation:** Invalid or unsupported paths/hosts/names may be rejected at load time or at first use. Restrict permissions to the minimum required for the pack to function. +- **Security:** Do not grant broader filesystem, network, or env access than necessary. Use explicit allowlists rather than wildcards unless the tooling documents safe patterns. diff --git a/src/content/docs/reference/shapes.md b/src/content/docs/reference/shapes.md index 353fef6..df70b20 100644 --- a/src/content/docs/reference/shapes.md +++ b/src/content/docs/reference/shapes.md @@ -1,63 +1,184 @@ --- title: Shapes -description: "Shape syntax: data models, field modifiers, composition, and type aliases." +description: Exhaustive reference for shape syntax: data models, field modifiers (required/optional, nullable), composition with base shapes, type aliases, export, and validation. --- - -Shapes define structured data (fields) or type aliases (base type + constraints). They are used for facts and `let` bindings. Optional fields use `?`; required non-null use `!`. Composition uses `with Base`. +Shapes define structured data (field-based models) or type aliases (a base type plus optional [constraints](/reference/constraints)). They are used as the type of [facts](/reference/facts), [let](/reference/let) bindings, and nested fields. Shape values are validated at runtime when assigned or passed as input. ## Syntax -**Data model:** +### Data model (complex shape) + +A shape with a body of named, typed fields: + ```text shape Name { + fieldName!: type + fieldName?: type + fieldName: type + fieldName!?: type +} +``` + +Each field has a name, an optional modifier suffix (`!`, `?`, or both in either order), a colon, and a type (any [type or shape](/reference/types-and-values)). + +### Type alias (simple shape) + +A shape that is an alias for a single type, optionally with constraints: + +```text +shape Name baseType +shape Name baseType @constraint1 @constraint2 +``` + +Examples: `shape ID string @uuid()`, `shape Score number @min(0) @max(100)`. + +### Composition (extending a base shape) + +A complex shape can extend exactly one other shape with `with BaseName` before the opening `{`. The base shape must be in scope (same namespace or [exported](/reference/namespaces) from another namespace). + +```text +shape Child with Base { field!: type - field?: type - field: type } ``` -**Alias:** `shape Name baseType @constraint` +The child shape’s effective fields are the union of the base’s fields and the child’s own fields. Duplicate field names between base and child are not allowed. -**Composition:** `shape Child with Base { ... }` +### Exporting a shape + +To make a shape visible to other namespaces (for use in [policy composition](/language-concepts/policy-composition) or as a fact type), declare: + +```text +export shape ShapeName +``` + +Only shapes declared in the same file (same namespace) can be exported. The shape name is the identifier used in the same file; other namespaces refer to it by the exported name. ## Configuration & Arguments -| Modifier | Description | -| :--- | :--- | -| `!` | Required, non-null. | -| `?` | Optional; may be omitted. | -| none | Required, may be null. | -| `!?` | Optional; if present, non-null. | +### Field modifiers (complex shapes) + +Every field in a shape body has two logical properties: whether the field is **required** (must be present in the value) and whether it is **nullable** (may be `null` when present). Modifiers are written as suffixes on the field name. + +| Modifier | Required? | Nullable? | Meaning | +| :--------- | :-------- | :-------- | :------ | +| (none) | Yes | Yes | Field must be present; value may be `null`. | +| `!` | Yes | No | Field must be present and must not be `null`. | +| `?` | No | Yes | Field may be omitted; if present, value may be `null`. | +| `!?` or `?!` | No | No | Field may be omitted; if present, value must not be `null`. | + +- **Required:** If the field is missing from the value at runtime, validation fails. +- **Nullable:** If the field is present and its value is `null`, validation fails when the field is non-nullable (`!` or `!?`/`?!`). -**Returns:** N/A (type definition). Values are validated when assigned or passed as facts. +Field type can be any type reference: primitives (`number`, `string`, `trinary`, `bool`, `document`), collections (`list[T]`, `map[T]`, `record[...]`), or another shape. Constraints on the type are validated when the value is assigned or when the value is supplied as a fact. + +### Composition rules + +| Aspect | Rule | +| :----- | :--- | +| Base shape | Exactly one `with BaseName`. The base must be a complex shape (or a type-alias shape that the implementation treats as a structural base where applicable). | +| Visibility | Base must be in the same namespace or exported from another namespace. Cross-namespace base requires the base shape to be declared with `export shape BaseName` in its file. | +| Duplicate fields | A child cannot declare a field with the same name as a field in the base. | +| Circular composition | Not allowed (e.g. A with B, B with A). | + +### Type alias (simple shape) rules + +| Aspect | Rule | +| :----- | :--- | +| Base type | Any single type: primitive, collection, or shape. | +| Constraints | Optional; use the same [constraint](/reference/constraints) syntax as on types (e.g. `@min(0)`, `@uuid()`). | +| Use | The shape name can be used anywhere a type is expected (facts, let, other shape fields, collections). | + +**Returns:** N/A (type definition). Values are validated when assigned to a variable or field of that shape, or when passed as facts. Validation failure aborts evaluation. + +## Where shapes can be used + +- **Facts:** A fact’s type can be a shape (e.g. `fact user: User`). The supplied JSON is validated against the shape’s fields and constraints. +- **Let bindings:** A let can be typed with a shape (e.g. `let u: User = someExpr`). The expression result is validated against the shape. +- **Nested types:** A shape field’s type can be another shape or `list[ShapeName]`, `map[ShapeName]`, etc. +- **Policy composition:** Imported policies can bind facts using shapes defined in another namespace if those shapes are exported. ## Examples in Action -### Typical use +### Data model with all modifier variants ```sentrie shape User { name!: string age: number email?: string + phone!?: string } ``` -### Going further +- `name` must be present and non-null. +- `age` must be present but may be null. +- `email` may be omitted; if present, may be null. +- `phone` may be omitted; if present, must not be null. + +### Checking optional fields in rules + +Optional or nullable fields should be checked before use. Use `is defined` to test presence (and often combine with a null check if the field is nullable). + +```sentrie +shape Item { name!: string; price?: number } + +rule showPrice = default "n/a" when item.price is defined { + yield cast item.price as string +} +``` + +### Type aliases with constraints + +```sentrie +shape ID string @uuid() +shape Permission string @one_of("read", "write", "delete") +shape Percent number @min(0) @max(100) +``` + +Use them in facts or let: + +```sentrie +fact userId: ID as id +let p: Percent = 50 +``` + +### Composition ```sentrie shape Base { id!: string } shape Extended with Base { role!: string } -shape ID string @uuid() ``` -## Good to Know +A value of type `Extended` must have both `id` and `role`, and both must be non-null (given `!`). -Before you implement this, keep a few boundaries in mind: +### Composition with exported base (cross-namespace) + +In file `auth.sentrie`: -- Composed shape includes all fields of Base plus its own. Circular composition is not allowed. -- Optional fields: use `is defined` to check before use. Exported shapes are visible across namespaces. +```sentrie +namespace com/example/auth + +shape Base { id!: string } +export shape Base +policy P { ... } +``` + +In another file in a different namespace, a shape can extend `Base` by referencing the exported name (see [Policy composition](/language-concepts/policy-composition) and namespace resolution). The base shape must be exported for cross-namespace composition. + +### Shape literals and field order + +When constructing a value (e.g. in a let or as input), field order in the literal does not affect type checking. All required fields must be present and all constraints must hold. + +## Good to Know + +Before you implement this, keep a few boundaries in mind: -- Unexported shapes are namespace-local. Field order in literals does not affect type checking. +- **Validation timing:** Shape (and constraint) validation runs when a value is assigned to a variable or field of that shape, or when facts are bound. Failure aborts evaluation. +- **Optional fields:** Use `is defined` (and null checks if the field is nullable) before using an optional or nullable field in expressions. Accessing a missing optional field yields `unknown` in expressions; the shape validator only checks that required fields are present and non-nullable fields are non-null when present. +- **Composition:** The composed shape effectively has all base fields plus its own. Circular composition (A with B, B with A, or longer cycles) is not allowed. Duplicate field names between base and child are not allowed. +- **Export:** Only shapes declared in the same file can be exported with `export shape Name`. Exported shapes are visible to other namespaces for use as types or as base shapes in composition. Unexported shapes are namespace-local. +- **Field order:** The order of fields in a shape literal does not affect type checking; only presence and types (and constraints) matter. +- **Naming:** Shape names and field names are identifiers. The same namespace can contain multiple shapes; policies in that namespace can reference any shape in the same namespace or any exported shape from another namespace (per import/resolution rules). diff --git a/src/content/docs/reference/trinary.md b/src/content/docs/reference/trinary.md index 97d335d..55ef7d5 100644 --- a/src/content/docs/reference/trinary.md +++ b/src/content/docs/reference/trinary.md @@ -1,73 +1,100 @@ --- title: Trinary Values -description: "Three-valued logic: true, false, unknown; truthiness and Kleene tables." +description: Exhaustive reference for three-valued logic: true, false, unknown; truthiness; Kleene AND/OR/NOT tables; and use in when, ternary, and Elvis. --- -Sentrie uses three values for logic and conditions: `true`, `false`, and `unknown`. Unknown covers cases where the result is indeterminate (e.g. an undefined field). When you write `when` guards or use ternary/Elvis, only `true` is treated as truthy; logical operators follow Kleene’s three-valued logic so unknown propagates in predictable ways. +Sentrie uses three values for logic and conditions: `true`, `false`, and `unknown`. `unknown` represents an indeterminate result (e.g. an optional field that is not present, or a comparison involving `unknown`). [Rule](/reference/rules) `when` guards, the [ternary](/reference/boolean-operations) operator (`? :`), and the Elvis operator (`?:`) use **truthiness**: only `true` is truthy; `false` and `unknown` are non-truthy. Logical operators (`and`, `or`, `xor`, `not`/`!`) follow **Kleene** three-valued logic so `unknown` propagates in defined ways. -Here is the basic syntax: +## Syntax -Literals: `true` | `false` | `unknown` +**Literals:** `true` | `false` | `unknown` -Logical: `and` | `or` | `xor` | `not` | `!` +**Logical operators (binary):** `and` | `or` | `xor` + +**Logical negation (unary):** `not expr` | `! expr` ## Configuration & Arguments -Truthiness and logical outcomes work like this: +### Truthiness + +Used by `when`, ternary (`? :`), and Elvis (`?:`): + +| Value | Truthy? | +| :-------- | :------ | +| `true` | Yes | +| `false` | No | +| `unknown` | No | + +So a rule with `when cond` runs its body only if `cond` evaluates to `true`; if `cond` is `false` or `unknown`, the rule uses its default (or `unknown`). -- **Truthiness:** Only `true` is truthy. `false` and `unknown` are non-truthy (e.g. for `when`, ternary, Elvis). -- **NOT:** One operand; result is trinary. +### NOT (unary) -| **Input** | Output | -| --- | --- | -| **true** | false | -| **false** | true | -| **unknown** | unknown | +One operand. The operand is interpreted as trinary; the result is trinary. -**Kleene AND** +| Input | Output | +| :-------- | :------- | +| `true` | `false` | +| `false` | `true` | +| `unknown` | `unknown` | -| **AND** | true | false | unknown | -| --- | --- | --- | --- | -| **true** | true | false | unknown | -| **false** | false | false | false | +### Kleene AND (binary) + +| AND | true | false | unknown | +| :-------- | :------ | :------ | :------ | +| **true** | true | false | unknown | +| **false** | false | false | false | | **unknown** | unknown | false | unknown | -**Kleene OR** +### Kleene OR (binary) -| **OR** | true | false | unknown | -| --- | --- | --- | --- | -| **true** | true | true | true | -| **false** | true | false | unknown | -| **unknown** | true | unknown | unknown | +| OR | true | false | unknown | +| :-------- | :------ | :------ | :------ | +| **true** | true | true | true | +| **false** | true | false | unknown | +| **unknown** | true | unknown | unknown | -**Returns:** Trinary. For `when` and conditionals, only `true` is truthy; `false` and `unknown` are not. +### XOR (binary) ---- +XOR is defined so that exactly one of the two operands is truthy for the result to be true. When either operand is `unknown`, the result is typically `unknown` (implementation-defined). See the runtime for the exact table. -## Examples in Action +**Returns:** Trinary. For `when` and conditionals, only `true` is truthy. -### Propagating unknown in expressions +## When unknown arises -You are chaining conditions and want to see how `unknown` behaves so your rules don’t accidentally allow or deny when data is missing. +- **Optional or missing field:** Accessing a field that is not present (e.g. on an optional shape field) may yield `unknown` or a type-specific default depending on context. +- **Comparisons:** Equality or ordering with `unknown` often yields `unknown` (e.g. `unknown == true` → `unknown`). +- **Logical propagation:** As in the Kleene tables, `and`/`or`/`not` propagate `unknown` instead of treating it as true or false. + +## Examples in Action + +### Propagating unknown ```sentrie let a = true and unknown -- unknown -let b = false or unknown -- unknown -let c = not unknown -- unknown +let b = false or unknown -- unknown +let c = not unknown -- unknown ``` -### Using unknown in a rule when +### When guard and unknown -You rely on a `when` guard; if the condition is unknown (e.g. optional field missing), the rule should fall back to its default instead of treating it as true. +If a `when` expression evaluates to `unknown` (e.g. optional field missing), the rule does not run its body; it uses its default or returns `unknown`: -In practice: `unknown` in a `when` causes the rule to use its default (or an `unknown` outcome). `not unknown` remains `unknown`. +```sentrie +rule allow = default false when user.role is defined { + yield user.role == "admin" +} +``` ---- +Here `user.role is defined` is false or unknown when the field is missing; the outcome is then `default false` (or `unknown` if no default). + +### Trinary constraints + +For runtime validation of trinary values (e.g. require `true` or forbid `unknown`), use [Constraints](/reference/constraints): trinary supports `@not_unknown()`, `@eq`, `@neq`, `@is_true()`, and `@is_false()`. ## Good to Know Before you implement this, keep a few boundaries in mind: -- **Constraint:** Undefined field access yields `unknown`; operations on `unknown` propagate according to the Kleene tables above. Rule `when` and ternary/Elvis use truthiness: only `true` is truthy. -- **Edge case:** If a `when` evaluates to `unknown`, the rule uses its default or returns `unknown`. `not unknown` is `unknown`. -- For runtime validation of trinary values (e.g. require `true` or forbid `unknown`), see [Constraints](/reference/constraints) — trinary supports `@not_unknown()`, `@eq`, `@neq`, `@is_true()`, and `@is_false()`. +- **Constraint:** Undefined or missing field access can yield `unknown`. Operations on `unknown` propagate according to the Kleene tables. Rule `when` and ternary/Elvis use truthiness: only `true` is truthy. +- **Edge case:** If a `when` evaluates to `unknown`, the rule uses its default or returns `unknown`. `not unknown` is `unknown`, so it is still non-truthy. +- **Trinary constraints:** See [Constraints](/reference/constraints) for `@not_unknown()`, `@eq`, `@neq`, `@is_true()`, `@is_false()` on trinary/bool types. diff --git a/src/content/docs/reference/types-and-values.md b/src/content/docs/reference/types-and-values.md index e122260..29b8156 100644 --- a/src/content/docs/reference/types-and-values.md +++ b/src/content/docs/reference/types-and-values.md @@ -1,10 +1,9 @@ --- title: Types and Values -description: Built-in primitive and collection types and type declarations. +description: Exhaustive reference for built-in primitive and collection types, type declarations, indexing, cast, and validation. --- - -Sentrie provides primitive types (`number`, `string`, `trinary`, `bool`, `document`) and collection types (`list[T]`, `map[T]`, `record[T1,T2,...]`). User-defined shapes extend these. Types can be used in `let`, `fact`, and shape fields. +Sentrie provides primitive types (`number`, `string`, `trinary`, `bool`, `document`) and collection types (`list[T]`, `map[T]`, `record[T1,T2,...]`). User-defined [shapes](/reference/shapes) extend these. Types can be used in [let](/reference/let), [facts](/reference/facts), and shape fields. Types may carry [constraints](/reference/constraints) for runtime validation. ## Syntax @@ -14,26 +13,52 @@ Sentrie provides primitive types (`number`, `string`, `trinary`, `bool`, `docume **Cast:** `cast expr as type` -List index: `expr[number]`. Map index: `expr[string]` or `expr.key`. +**Indexing:** +- List/record: `expr[number]` (zero-based index). +- Map: `expr[string]` or `expr.identifier` (key must be a string; dot form when the key is a valid identifier). ## Configuration & Arguments -| Type | Description | -| :--- | :--- | -| `number` | float64. | -| `string` | UTF-8 string. | -| `trinary` | `true` \| `false` \| `unknown`. | -| `bool` | `true` \| `false` (subset of trinary). | -| `document` | JSON-like object. | -| `list[T]` | Ordered list of T; index by number. | -| `map[T]` | String keys, T values; keys must be strings. | -| `record[T1,T2,...]` | Fixed-length tuple. | +### Primitive types + +| Type | Description | Notes | +| :--- | :---------- | :---- | +| `number` | 64-bit floating-point (float64). | All numeric literals and arithmetic use this. No separate integer type. | +| `string` | UTF-8 string. | Literals: double-quoted or backtick. | +| `trinary` | Three-valued logic: `true`, `false`, `unknown`. | Used for conditions and [boolean operations](/reference/boolean-operations). See [Trinary](/reference/trinary). | +| `bool` | Two-valued: `true`, `false`. | Subset of trinary; no `unknown` literal for bool, but expressions can produce trinary. | +| `document` | JSON-like object (string keys, arbitrary values). | Used for untyped or semi-structured data. | + +### Collection types + +| Type | Description | Index type | Notes | +| :--- | :---------- | :--------- | :---- | +| `list[T]` | Ordered sequence of values of type `T`. | `number` (zero-based). | Index out of range is an error or undefined behavior per implementation. | +| `map[T]` | Key-value map; keys are strings, values are type `T`. | `string` or `.key` (identifier). | Keys must be strings. Dot access: `map.key` when key is a valid identifier. | +| `record[T1,T2,...]` | Fixed-length tuple with typed positions. | `number` (zero-based position). | Length and types are fixed at type declaration. | + +### Cast + +`cast expr as type` converts the value of `expr` to the given type. The result is validated against the type and any [constraints](/reference/constraints) on that type. If conversion or validation fails, evaluation aborts. Supported conversions include string↔number, number↔string, bool↔string, and literal document to `document`. + +**Returns:** N/A for type declarations. For `cast`: the value after conversion and validation; failure aborts evaluation. + +## Indexing and access -**Returns:** N/A for types. `cast` returns the value after validation against the target type (and constraints); fails if invalid. +- **List:** `listExpr[indexExpr]`. `indexExpr` must be a number (or evaluate to a number). Zero-based. Bounds behavior is implementation-defined (error or undefined). +- **Record:** `recordExpr[indexExpr]`. Same as list; index is the position (0, 1, 2, …). +- **Map:** `mapExpr[keyExpr]` or `mapExpr.key`. Key must be a string. Dot form is equivalent to bracket form when the key is a valid identifier (e.g. `m.one` ≈ `m["one"]`). + +## Where types are used + +- **Facts:** Fact type is a shape, primitive, or collection (e.g. `fact user: User`, `fact id: string`). +- **Let:** Optional type annotation: `let x: number = 5`. When present, value is validated at runtime. +- **Shape fields:** Each field has a type (primitive, collection, or shape). +- **Constraints:** Types can be constrained (e.g. `number @min(0) @max(100)`). See [Constraints](/reference/constraints). ## Examples in Action -### Typical use +### Primitives and collections ```text let u: number = 50 @@ -44,22 +69,28 @@ let m: map[number] = { "one": 1, "two": 2 } let r: record[string, number, bool] = ["one", 1, true] ``` -### Going further +### Indexing ```text let first: number = arr[0] let one: number = m["one"] let oneAlt: number = m.one +``` + +### Cast + +```text let x: number = cast "50" as number +let y: string = cast 50 as string +let z: bool = cast "true" as bool +let doc: document = cast { "name": "John", "age": 30 } as document ``` ## Good to Know Before you implement this, keep a few boundaries in mind: -- Type annotation on `let` is optional; when omitted, value is not validated against a type. -- Map keys must be strings. Division by zero aborts evaluation. Constraint failure aborts evaluation. - - -- Map keys must be strings. Access with `[index]`: number for list/record, string for map. -- `cast` validates against the target type and any constraints; failure aborts evaluation. +- **Type annotation on let:** Optional. When omitted, the value is not validated against a type. When present, the value is validated (and constraints apply); failure aborts evaluation. +- **Map keys:** Must be strings. Access with `[index]`: numeric index for list/record, string key for map. Dot access for maps only when the key is a valid identifier. +- **Division by zero:** Aborts evaluation. [Constraint](/reference/constraints) failure also aborts evaluation. +- **Cast:** Validates against the target type and any constraints; failure aborts evaluation. Not all type combinations may be supported; see implementation for allowed conversions. From 145a863a04bc4b5ef25c89b407d941b0003ad540 Mon Sep 17 00:00:00 2001 From: Binaek Sarkar Date: Sun, 22 Feb 2026 14:06:07 +0530 Subject: [PATCH 12/21] Fixing issue where frontmatter contained a colon - leading to parsing issues --- .../docs/reference/collection-operations.md | 38 +++++++++---------- src/content/docs/reference/facts.md | 2 +- .../docs/reference/membership-operations.md | 2 +- .../reference/security-and-permissions.md | 2 +- src/content/docs/reference/shapes.md | 2 +- src/content/docs/reference/trinary.md | 2 +- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/content/docs/reference/collection-operations.md b/src/content/docs/reference/collection-operations.md index f12e513..46a123e 100644 --- a/src/content/docs/reference/collection-operations.md +++ b/src/content/docs/reference/collection-operations.md @@ -1,6 +1,6 @@ --- title: Collection Operations -description: Exhaustive reference for quantifiers and transformers: any, all, filter, map, reduce, count, distinct; syntax, parameters, and edge cases. +description: "Exhaustive reference for quantifiers and transformers (any, all, filter, map, reduce, count, distinct); syntax, parameters, and edge cases." --- Collection operations apply to lists and maps. They are declarative: they return new values or new collections and do not mutate the input. Syntax uses a block with a single `yield` per iteration. The collection is iterated in order (list order or map iteration order); for `reduce`, an initial value is combined with each element via the yielded expression. @@ -22,15 +22,15 @@ distinct collection ## Configuration & Arguments -| Operation | Input | Output | Description | -| :-------- | :---- | :----- | :---------- | -| `any` | collection | bool/trinary | true if at least one element yields a truthy value. Short-circuits: iteration stops at first truthy. Empty collection → false. | -| `all` | collection | bool/trinary | true if every element yields a truthy value. Short-circuits: iteration stops at first falsy. Empty collection → true. | -| `filter` | collection | same type | New collection containing only elements for which the block yields truthy. | -| `map` | collection | list | New list whose elements are the yielded values (one per element). Type of each yield can be any type. | -| `reduce` | collection, initial | type of initial | Fold: start with `acc = initial`; for each element, set `acc` to the yielded expression. Final `acc` is the result. Empty collection → returns initial. | -| `count` | collection | number | Number of elements in the collection. Empty → 0. | -| `distinct` | collection | same type | New collection with duplicate elements removed. Equality for deduplication is by the language’s `==`. | +| Operation | Input | Output | Description | +| :--------- | :------------------ | :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `any` | collection | bool/trinary | true if at least one element yields a truthy value. Short-circuits: iteration stops at first truthy. Empty collection → false. | +| `all` | collection | bool/trinary | true if every element yields a truthy value. Short-circuits: iteration stops at first falsy. Empty collection → true. | +| `filter` | collection | same type | New collection containing only elements for which the block yields truthy. | +| `map` | collection | list | New list whose elements are the yielded values (one per element). Type of each yield can be any type. | +| `reduce` | collection, initial | type of initial | Fold: start with `acc = initial`; for each element, set `acc` to the yielded expression. Final `acc` is the result. Empty collection → returns initial. | +| `count` | collection | number | Number of elements in the collection. Empty → 0. | +| `distinct` | collection | same type | New collection with duplicate elements removed. Equality for deduplication is by the language’s `==`. | **Returns:** As in the table. For `any`/`all`, the result is the trinary/boolean produced by the predicate. For `filter`/`map`/`distinct`, the result is a new collection (or list for `map`). For `reduce`, the result is the final accumulator. For `count`, the result is a number. @@ -42,15 +42,15 @@ distinct collection ## Empty collection behavior -| Operation | Empty collection result | -| :-------- | :---------------------- | -| `any` | false | -| `all` | true | -| `filter` | empty collection | -| `map` | empty list | -| `reduce` | initial (unchanged) | -| `count` | 0 | -| `distinct` | empty collection | +| Operation | Empty collection result | +| :--------- | :---------------------- | +| `any` | false | +| `all` | true | +| `filter` | empty collection | +| `map` | empty list | +| `reduce` | initial (unchanged) | +| `count` | 0 | +| `distinct` | empty collection | ## Examples in Action diff --git a/src/content/docs/reference/facts.md b/src/content/docs/reference/facts.md index 405a3b9..f411d10 100644 --- a/src/content/docs/reference/facts.md +++ b/src/content/docs/reference/facts.md @@ -1,6 +1,6 @@ --- title: Facts -description: Exhaustive reference for fact declaration syntax: required/optional, type, alias, default, and binding at evaluation. +description: "Exhaustive reference for fact declaration syntax (required/optional, type, alias, default) and binding at evaluation." --- Facts are named, typed inputs to a [policy](/reference/policies). They are declared at the top of the policy (before [let](/reference/let), [use](/reference/functions), and [rules](/reference/rules)). Each fact has a name, a type (shape or primitive), an optional alias used in the policy body, and—only for optional facts—an optional default expression. Required facts must be supplied at evaluation time or evaluation fails. Facts are non-nullable when supplied: null is not allowed as a fact value. diff --git a/src/content/docs/reference/membership-operations.md b/src/content/docs/reference/membership-operations.md index 8033874..31c388a 100644 --- a/src/content/docs/reference/membership-operations.md +++ b/src/content/docs/reference/membership-operations.md @@ -1,6 +1,6 @@ --- title: Membership Operations -description: Exhaustive reference for containment operators: in and contains (list, map, string); semantics and edge cases. +description: "Exhaustive reference for containment operators (in, contains) for list, map, and string; semantics and edge cases." --- The `in` and `contains` operators test containment: whether a value appears in a list, a key exists in a map, a substring appears in a string, or (for maps) one map is a “subset” of another. Both return a boolean. `needle in haystack` and `haystack contains needle` are equivalent; only the argument order differs. diff --git a/src/content/docs/reference/security-and-permissions.md b/src/content/docs/reference/security-and-permissions.md index 4292723..36546ca 100644 --- a/src/content/docs/reference/security-and-permissions.md +++ b/src/content/docs/reference/security-and-permissions.md @@ -1,6 +1,6 @@ --- title: Security and Permissions -description: Exhaustive reference for policy pack permissions: filesystem read, network, and environment variables; configuration and defaults. +description: "Exhaustive reference for policy pack permissions (filesystem read, network, environment variables); configuration and defaults." --- Permissions define what a policy pack can access at runtime: which filesystem paths can be read, which network hosts can be contacted, and which environment variable names are exposed to the pack. They are configured in the pack configuration file (e.g. `sentrie.pack.toml`) under a `[permissions]` section. Default behavior is restrictive: typically only the pack root is readable; no network access; no environment variables. Explicit entries grant access only to the listed paths, hosts, and variable names. diff --git a/src/content/docs/reference/shapes.md b/src/content/docs/reference/shapes.md index df70b20..1ba345f 100644 --- a/src/content/docs/reference/shapes.md +++ b/src/content/docs/reference/shapes.md @@ -1,6 +1,6 @@ --- title: Shapes -description: Exhaustive reference for shape syntax: data models, field modifiers (required/optional, nullable), composition with base shapes, type aliases, export, and validation. +description: "Exhaustive reference for shape syntax (data models, field modifiers, composition, type aliases, export) and validation." --- Shapes define structured data (field-based models) or type aliases (a base type plus optional [constraints](/reference/constraints)). They are used as the type of [facts](/reference/facts), [let](/reference/let) bindings, and nested fields. Shape values are validated at runtime when assigned or passed as input. diff --git a/src/content/docs/reference/trinary.md b/src/content/docs/reference/trinary.md index 55ef7d5..1607001 100644 --- a/src/content/docs/reference/trinary.md +++ b/src/content/docs/reference/trinary.md @@ -1,6 +1,6 @@ --- title: Trinary Values -description: Exhaustive reference for three-valued logic: true, false, unknown; truthiness; Kleene AND/OR/NOT tables; and use in when, ternary, and Elvis. +description: "Exhaustive reference for three-valued logic (true, false, unknown), truthiness, Kleene AND/OR/NOT tables, and use in when, ternary, and Elvis." --- Sentrie uses three values for logic and conditions: `true`, `false`, and `unknown`. `unknown` represents an indeterminate result (e.g. an optional field that is not present, or a comparison involving `unknown`). [Rule](/reference/rules) `when` guards, the [ternary](/reference/boolean-operations) operator (`? :`), and the Elvis operator (`?:`) use **truthiness**: only `true` is truthy; `false` and `unknown` are non-truthy. Logical operators (`and`, `or`, `xor`, `not`/`!`) follow **Kleene** three-valued logic so `unknown` propagates in defined ways. From 48688bb4b220a0003a770daa7f3eb9e5217e3ce7 Mon Sep 17 00:00:00 2001 From: Binaek Sarkar Date: Sun, 22 Feb 2026 14:09:21 +0530 Subject: [PATCH 13/21] Refactor descriptions in documentation to improve clarity and consistency across multiple sections --- src/content/docs/reference/arithmetic-operations.md | 2 +- src/content/docs/reference/boolean-operations.md | 2 +- src/content/docs/reference/collection-operations.md | 2 +- src/content/docs/reference/constraints.md | 2 +- src/content/docs/reference/facts.md | 2 +- src/content/docs/reference/functions.md | 2 +- src/content/docs/reference/index.md | 4 ++-- src/content/docs/reference/let.md | 2 +- src/content/docs/reference/membership-operations.md | 2 +- src/content/docs/reference/namespaces.md | 2 +- src/content/docs/reference/policies.md | 2 +- src/content/docs/reference/precedence.md | 2 +- src/content/docs/reference/rules.md | 2 +- src/content/docs/reference/security-and-permissions.md | 2 +- src/content/docs/reference/shapes.md | 2 +- src/content/docs/reference/trinary.md | 2 +- src/content/docs/reference/types-and-values.md | 2 +- 17 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/content/docs/reference/arithmetic-operations.md b/src/content/docs/reference/arithmetic-operations.md index ed43120..0011f54 100644 --- a/src/content/docs/reference/arithmetic-operations.md +++ b/src/content/docs/reference/arithmetic-operations.md @@ -1,6 +1,6 @@ --- title: Arithmetic Operations -description: Exhaustive reference for arithmetic operators (+, -, *, /, %), unary +/-, operand types, and edge cases including division by zero. +description: "Arithmetic operators (+, -, *, /, %), unary +/-, operand types, and edge cases including division by zero." --- Arithmetic operators operate on `number` (float64). All numeric operands are treated as `number`; there is no separate integer type. The result of every arithmetic operation is `number`. Division by zero and modulo by zero abort evaluation with an error. diff --git a/src/content/docs/reference/boolean-operations.md b/src/content/docs/reference/boolean-operations.md index a7f2a2e..6fe6fa5 100644 --- a/src/content/docs/reference/boolean-operations.md +++ b/src/content/docs/reference/boolean-operations.md @@ -1,6 +1,6 @@ --- title: Boolean Operations -description: Exhaustive reference for logical (and, or, xor, not), comparison (==, !=, <, <=, >, >=), pattern (matches), and conditional (ternary, Elvis) operators. +description: "Logical (and, or, xor, not), comparison (==, !=, <, <=, >, >=), pattern (matches), and conditional (ternary, Elvis) operators." --- Boolean operations combine conditions, compare values, and branch on truthiness. Logical and comparison operators use [trinary](/reference/trinary) semantics (true, false, unknown). The pattern operator `matches` works on strings and returns a boolean. The ternary (`? :`) and Elvis (`?:`) operators choose a value based on whether a condition is truthy; only `true` is truthy. diff --git a/src/content/docs/reference/collection-operations.md b/src/content/docs/reference/collection-operations.md index 46a123e..a4411e9 100644 --- a/src/content/docs/reference/collection-operations.md +++ b/src/content/docs/reference/collection-operations.md @@ -1,6 +1,6 @@ --- title: Collection Operations -description: "Exhaustive reference for quantifiers and transformers (any, all, filter, map, reduce, count, distinct); syntax, parameters, and edge cases." +description: "Quantifiers and transformers (any, all, filter, map, reduce, count, distinct); syntax, parameters, and edge cases." --- Collection operations apply to lists and maps. They are declarative: they return new values or new collections and do not mutate the input. Syntax uses a block with a single `yield` per iteration. The collection is iterated in order (list order or map iteration order); for `reduce`, an initial value is combined with each element via the yielded expression. diff --git a/src/content/docs/reference/constraints.md b/src/content/docs/reference/constraints.md index 242c26c..d6e9559 100644 --- a/src/content/docs/reference/constraints.md +++ b/src/content/docs/reference/constraints.md @@ -1,6 +1,6 @@ --- title: Constraints -description: Exhaustive reference for constraint syntax and all constraints (e.g. @min, @max, @email) on number, string, list, and trinary types. +description: "Constraint syntax and all constraints (e.g. @min, @max, @email) on number, string, list, and trinary types." --- Constraints validate values at runtime using the `@` syntax on types. They apply to primitives, collection elements, and shape fields. Validation failure aborts evaluation. diff --git a/src/content/docs/reference/facts.md b/src/content/docs/reference/facts.md index f411d10..0e55b2c 100644 --- a/src/content/docs/reference/facts.md +++ b/src/content/docs/reference/facts.md @@ -1,6 +1,6 @@ --- title: Facts -description: "Exhaustive reference for fact declaration syntax (required/optional, type, alias, default) and binding at evaluation." +description: "Fact declaration syntax (required/optional, type, alias, default) and binding at evaluation." --- Facts are named, typed inputs to a [policy](/reference/policies). They are declared at the top of the policy (before [let](/reference/let), [use](/reference/functions), and [rules](/reference/rules)). Each fact has a name, a type (shape or primitive), an optional alias used in the policy body, and—only for optional facts—an optional default expression. Required facts must be supplied at evaluation time or evaluation fails. Facts are non-nullable when supplied: null is not allowed as a fact value. diff --git a/src/content/docs/reference/functions.md b/src/content/docs/reference/functions.md index fddd33e..baf1a1b 100644 --- a/src/content/docs/reference/functions.md +++ b/src/content/docs/reference/functions.md @@ -1,6 +1,6 @@ --- title: Functions -description: Exhaustive reference for function call syntax, TypeScript module import (use), aliasing, and memoization. +description: "Function call syntax, TypeScript module import (use), aliasing, and memoization." --- Functions in Sentrie are provided by TypeScript modules. There are no built-in global functions in the language; all callable functions come from modules imported with `use` inside a [policy](/reference/policies). Calls use the form `functionName(args...)` for functions imported without an alias, or `alias.functionName(args...)` when the module is imported with an alias. diff --git a/src/content/docs/reference/index.md b/src/content/docs/reference/index.md index 004cbf8..05df310 100644 --- a/src/content/docs/reference/index.md +++ b/src/content/docs/reference/index.md @@ -1,9 +1,9 @@ --- title: Policy Language Reference -description: Exhaustive dictionary of Sentrie language syntax, types, operators, and constructs. +description: "Reference for Sentrie language syntax, types, operators, and constructs." --- -This section is a strict reference for the Sentrie policy language. Every page is intended to be exhaustive: full syntax variants, all options and arguments in tables, when and where each feature applies, edge cases, and multiple examples. For conceptual overviews and “how it works,” see [Language Concepts](/language-concepts/type-system-shapes). +This section is a reference for the Sentrie policy language: syntax, types, operators, and constructs. Pages include syntax variants, options and arguments in tables, where each feature applies, edge cases, and examples. For conceptual overviews and “how it works,” see [Language Concepts](/language-concepts/type-system-shapes). ## Program structure diff --git a/src/content/docs/reference/let.md b/src/content/docs/reference/let.md index 5021c49..74d4e55 100644 --- a/src/content/docs/reference/let.md +++ b/src/content/docs/reference/let.md @@ -1,6 +1,6 @@ --- title: Intermediate Values (let) -description: Exhaustive reference for let declaration syntax, scoping, immutability, and type validation. +description: "let declaration syntax, scoping, immutability, and type validation." --- `let` binds a name to an expression inside a block. It is used for intermediate values in a [policy](/reference/policies) or inside a [rule](/reference/rules) body. The binding is scoped to the block, is immutable (no reassignment), and cannot be exported. Only [rules](/reference/rules) can be exported. If a type annotation is present, the value is validated at runtime (including [constraints](/reference/constraints)); validation failure aborts evaluation. diff --git a/src/content/docs/reference/membership-operations.md b/src/content/docs/reference/membership-operations.md index 31c388a..d94def0 100644 --- a/src/content/docs/reference/membership-operations.md +++ b/src/content/docs/reference/membership-operations.md @@ -1,6 +1,6 @@ --- title: Membership Operations -description: "Exhaustive reference for containment operators (in, contains) for list, map, and string; semantics and edge cases." +description: "Containment operators (in, contains) for list, map, and string; semantics and edge cases." --- The `in` and `contains` operators test containment: whether a value appears in a list, a key exists in a map, a substring appears in a string, or (for maps) one map is a “subset” of another. Both return a boolean. `needle in haystack` and `haystack contains needle` are equivalent; only the argument order differs. diff --git a/src/content/docs/reference/namespaces.md b/src/content/docs/reference/namespaces.md index f6cf888..244e9cd 100644 --- a/src/content/docs/reference/namespaces.md +++ b/src/content/docs/reference/namespaces.md @@ -1,6 +1,6 @@ --- title: Namespaces -description: Exhaustive reference for namespace syntax, placement rules, visibility of shapes and policies, and cross-file behavior. +description: "Namespace syntax, placement rules, visibility of shapes and policies, and cross-file behavior." --- Namespaces group [policies](/reference/policies) and [shapes](/reference/shapes) and define visibility boundaries. Each `*.sentrie` file has exactly one namespace declaration. The namespace must be the first statement in the file (only comments may appear before it). Unexported shapes are visible only within the same namespace; exported shapes can be referenced from other namespaces. diff --git a/src/content/docs/reference/policies.md b/src/content/docs/reference/policies.md index 7578ae9..6152305 100644 --- a/src/content/docs/reference/policies.md +++ b/src/content/docs/reference/policies.md @@ -1,6 +1,6 @@ --- title: Policies -description: Exhaustive reference for policy syntax, statement order (facts, let, use, rules, export), and evaluation behavior. +description: "Policy syntax, statement order (facts, let, use, rules, export), and evaluation behavior." --- A policy is a named block inside a [namespace](/reference/namespaces) that groups [facts](/reference/facts), optional [let](/reference/let) bindings, optional [use](/reference/functions) imports, [rules](/reference/rules), and one or more `export decision of` declarations. It defines the inputs and decision logic that the CLI or HTTP API can execute. To be runnable or importable, a policy must have at least one rule and at least one exported decision. diff --git a/src/content/docs/reference/precedence.md b/src/content/docs/reference/precedence.md index 26484a4..dd4f85f 100644 --- a/src/content/docs/reference/precedence.md +++ b/src/content/docs/reference/precedence.md @@ -1,6 +1,6 @@ --- title: Precedence -description: Exhaustive reference for operator precedence (highest to lowest), associativity, and use of parentheses. +description: "Operator precedence (highest to lowest), associativity, and use of parentheses." --- Operators are evaluated in order of precedence: higher precedence binds first. When two operators have the same precedence, they are usually evaluated left-to-right; the ternary operator `? :` is right-associative. Parentheses `( ... )` override precedence and explicitly group subexpressions. diff --git a/src/content/docs/reference/rules.md b/src/content/docs/reference/rules.md index 020a8e4..dd39df3 100644 --- a/src/content/docs/reference/rules.md +++ b/src/content/docs/reference/rules.md @@ -1,6 +1,6 @@ --- title: Rules -description: Exhaustive reference for rule syntax, when/default/body evaluation, outcome type, and cross-references. +description: "Rule syntax, when/default/body evaluation, outcome type, and cross-references." --- A rule defines a single named decision: an optional `when` guard, an optional `default` value when the guard is not truthy, and a body that must contain exactly one `yield`. If the `when` expression is truthy, the body is evaluated and its `yield` value is the rule’s outcome; otherwise the outcome is the `default` (or `unknown` if no default is given). Rules are the only construct that can be exported for execution or import. diff --git a/src/content/docs/reference/security-and-permissions.md b/src/content/docs/reference/security-and-permissions.md index 36546ca..0ee3c44 100644 --- a/src/content/docs/reference/security-and-permissions.md +++ b/src/content/docs/reference/security-and-permissions.md @@ -1,6 +1,6 @@ --- title: Security and Permissions -description: "Exhaustive reference for policy pack permissions (filesystem read, network, environment variables); configuration and defaults." +description: "Policy pack permissions (filesystem read, network, environment variables); configuration and defaults." --- Permissions define what a policy pack can access at runtime: which filesystem paths can be read, which network hosts can be contacted, and which environment variable names are exposed to the pack. They are configured in the pack configuration file (e.g. `sentrie.pack.toml`) under a `[permissions]` section. Default behavior is restrictive: typically only the pack root is readable; no network access; no environment variables. Explicit entries grant access only to the listed paths, hosts, and variable names. diff --git a/src/content/docs/reference/shapes.md b/src/content/docs/reference/shapes.md index 1ba345f..f739f3d 100644 --- a/src/content/docs/reference/shapes.md +++ b/src/content/docs/reference/shapes.md @@ -1,6 +1,6 @@ --- title: Shapes -description: "Exhaustive reference for shape syntax (data models, field modifiers, composition, type aliases, export) and validation." +description: "Shape syntax (data models, field modifiers, composition, type aliases, export) and validation." --- Shapes define structured data (field-based models) or type aliases (a base type plus optional [constraints](/reference/constraints)). They are used as the type of [facts](/reference/facts), [let](/reference/let) bindings, and nested fields. Shape values are validated at runtime when assigned or passed as input. diff --git a/src/content/docs/reference/trinary.md b/src/content/docs/reference/trinary.md index 1607001..591ef58 100644 --- a/src/content/docs/reference/trinary.md +++ b/src/content/docs/reference/trinary.md @@ -1,6 +1,6 @@ --- title: Trinary Values -description: "Exhaustive reference for three-valued logic (true, false, unknown), truthiness, Kleene AND/OR/NOT tables, and use in when, ternary, and Elvis." +description: "Three-valued logic (true, false, unknown), truthiness, Kleene AND/OR/NOT tables, and use in when, ternary, and Elvis." --- Sentrie uses three values for logic and conditions: `true`, `false`, and `unknown`. `unknown` represents an indeterminate result (e.g. an optional field that is not present, or a comparison involving `unknown`). [Rule](/reference/rules) `when` guards, the [ternary](/reference/boolean-operations) operator (`? :`), and the Elvis operator (`?:`) use **truthiness**: only `true` is truthy; `false` and `unknown` are non-truthy. Logical operators (`and`, `or`, `xor`, `not`/`!`) follow **Kleene** three-valued logic so `unknown` propagates in defined ways. diff --git a/src/content/docs/reference/types-and-values.md b/src/content/docs/reference/types-and-values.md index 29b8156..d6de01f 100644 --- a/src/content/docs/reference/types-and-values.md +++ b/src/content/docs/reference/types-and-values.md @@ -1,6 +1,6 @@ --- title: Types and Values -description: Exhaustive reference for built-in primitive and collection types, type declarations, indexing, cast, and validation. +description: "Built-in primitive and collection types, type declarations, indexing, cast, and validation." --- Sentrie provides primitive types (`number`, `string`, `trinary`, `bool`, `document`) and collection types (`list[T]`, `map[T]`, `record[T1,T2,...]`). User-defined [shapes](/reference/shapes) extend these. Types can be used in [let](/reference/let), [facts](/reference/facts), and shape fields. Types may carry [constraints](/reference/constraints) for runtime validation. From 4b2154c4ba977f0cdbf457e3594517765691ba77 Mon Sep 17 00:00:00 2001 From: Binaek Sarkar Date: Sun, 22 Feb 2026 14:17:50 +0530 Subject: [PATCH 14/21] Refactor GitHub Actions workflow for PR previews to dynamically detect package manager and streamline build process --- .github/workflows/pr-preview.yml | 60 ++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml index b3032dc..c4f05f0 100644 --- a/.github/workflows/pr-preview.yml +++ b/.github/workflows/pr-preview.yml @@ -11,31 +11,54 @@ permissions: concurrency: group: pr-preview-${{ github.event.pull_request.number }} +env: + BUILD_PATH: "." + PREVIEW_URL: "https://sentrie-pr-${{ github.event.pull_request.number }}.surge.sh" + jobs: deploy-preview: name: Deploy Preview runs-on: ubuntu-latest steps: - - name: Checkout code + - name: Checkout uses: actions/checkout@v4 - - name: Setup Node.js + - name: Detect package manager + id: detect-package-manager + run: | + if [ -f "${{ github.workspace }}/yarn.lock" ]; then + echo "manager=yarn" >> $GITHUB_OUTPUT + echo "command=install" >> $GITHUB_OUTPUT + echo "runner=yarn" >> $GITHUB_OUTPUT + echo "lockfile=yarn.lock" >> $GITHUB_OUTPUT + exit 0 + elif [ -f "${{ github.workspace }}/package.json" ]; then + echo "manager=npm" >> $GITHUB_OUTPUT + echo "command=ci" >> $GITHUB_OUTPUT + echo "runner=npx --no-install" >> $GITHUB_OUTPUT + echo "lockfile=package-lock.json" >> $GITHUB_OUTPUT + exit 0 + else + echo "Unable to determine package manager" + exit 1 + fi + + - name: Setup Node uses: actions/setup-node@v4 with: node-version: "20" - cache: "npm" + cache: ${{ steps.detect-package-manager.outputs.manager }} + cache-dependency-path: ${{ env.BUILD_PATH }}/${{ steps.detect-package-manager.outputs.lockfile }} - name: Install dependencies - run: | - npm cache clean --force - rm -rf node_modules package-lock.json - npm install + run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }} + working-directory: ${{ env.BUILD_PATH }} - - name: Build Astro site - env: - ASTRO_PREVIEW: "true" - ASTRO_SITE_URL: "https://sentrie-pr-${{ github.event.pull_request.number }}.surge.sh" - run: npm run build + - name: Build with Astro + run: | + ${{ steps.detect-package-manager.outputs.runner }} astro build \ + --site "${{ env.PREVIEW_URL }}" + working-directory: ${{ env.BUILD_PATH }} - name: Install Surge run: npm install --global surge @@ -46,17 +69,16 @@ jobs: run: | cd dist surge --project . \ - --domain "sentrie-pr-${{ github.event.pull_request.number }}.surge.sh" \ + --domain "${{ env.PREVIEW_URL }}" \ -y - name: Verify deployment timeout-minutes: 2 run: | - PREVIEW_URL="https://sentrie-pr-${{ github.event.pull_request.number }}.surge.sh" - echo "Verifying deployment at $PREVIEW_URL..." + echo "Verifying deployment at ${{ env.PREVIEW_URL }}..." for i in {1..10}; do - if curl -fsSL -o /dev/null "$PREVIEW_URL"; then - echo "Deployment verified successfully!" + if curl -fsSL -o /dev/null "{{ env.PREVIEW_URL }}"; then + echo "Deployment verified successfully at ${{ env.PREVIEW_URL }}!" exit 0 fi echo "Attempt $i/10 failed, retrying in 2 seconds..." @@ -83,8 +105,8 @@ jobs: ## 🚀 Preview Deployment - A preview of this PR has been deployed to: + A preview of this PR has been deployed to ${{ env.PREVIEW_URL }}: - 🔗 **[View Preview](https://sentrie-pr-${{ github.event.pull_request.number }}.surge.sh)** + 🔗 **[View Preview](${{ env.PREVIEW_URL }}) at ${{ env.PREVIEW_URL }}** This preview will be automatically removed when the PR is merged or closed. From 5c31f785b247fa78126bb34b2cf9ef97f6a1566c Mon Sep 17 00:00:00 2001 From: Binaek Sarkar Date: Sun, 22 Feb 2026 14:19:55 +0530 Subject: [PATCH 15/21] Fix syntax in GitHub Actions workflow to correctly reference environment variable for deployment verification --- .github/workflows/pr-preview.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml index c4f05f0..2681b32 100644 --- a/.github/workflows/pr-preview.yml +++ b/.github/workflows/pr-preview.yml @@ -77,7 +77,7 @@ jobs: run: | echo "Verifying deployment at ${{ env.PREVIEW_URL }}..." for i in {1..10}; do - if curl -fsSL -o /dev/null "{{ env.PREVIEW_URL }}"; then + if curl -fsSL -o /dev/null "${{ env.PREVIEW_URL }}"; then echo "Deployment verified successfully at ${{ env.PREVIEW_URL }}!" exit 0 fi From 4c6d9b32cc147df3e8db0387f1b45f280955094d Mon Sep 17 00:00:00 2001 From: Binaek Sarkar Date: Sun, 22 Feb 2026 14:26:39 +0530 Subject: [PATCH 16/21] Add identifiers section to documentation and update references --- astro.config.mjs | 1 + src/content/docs/reference/identifiers.md | 40 +++++++++++++++++++ src/content/docs/reference/index.md | 1 + src/content/docs/reference/namespaces.md | 4 +- src/content/docs/reference/shapes.md | 2 +- .../docs/reference/types-and-values.md | 8 ++-- 6 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 src/content/docs/reference/identifiers.md diff --git a/astro.config.mjs b/astro.config.mjs index 97571e8..4e2d218 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -51,6 +51,7 @@ export default defineConfig({ { label: "Rules", slug: "reference/rules" }, { label: "Facts", slug: "reference/facts" }, { label: "Intermediate Values (let)", slug: "reference/let" }, + { label: "Identifiers", slug: "reference/identifiers" }, { label: "Types and Values", slug: "reference/types-and-values" }, { label: "Constraints", slug: "reference/constraints" }, { label: "Trinary Values", slug: "reference/trinary" }, diff --git a/src/content/docs/reference/identifiers.md b/src/content/docs/reference/identifiers.md new file mode 100644 index 0000000..f3529de --- /dev/null +++ b/src/content/docs/reference/identifiers.md @@ -0,0 +1,40 @@ +--- +title: Identifiers +description: "What counts as a valid identifier: syntax rule, allowed characters, and reserved keywords." +--- + +Identifiers are names used for namespaces, policies, rules, facts, let bindings, shape and field names, aliases, and for map dot access keys. The lexer recognizes a **valid identifier** by a single rule. + +## Syntax + +An identifier is a token that: + +1. **Starts with** a letter (`a`–`z`, `A`–`Z`) or an underscore (`_`). +2. **Continues with** zero or more of: letters (`a`–`z`, `A`–`Z`), digits (`0`–`9`), or underscores (`_`). + +So: only ASCII letters, digits, and underscores. No spaces, hyphens, or other characters. The first character must not be a digit. + +## Examples + +| Valid | Invalid (reason) | +| :------ | :----------------------- | +| `x` | `2x` (starts with digit) | +| `name` | `my-name` (hyphen) | +| `_priv` | `my name` (space) | +| `a1` | `a.b` (dot) | +| `com` | | + +## Reserved keywords + +Language keywords are reserved and cannot be used as identifiers. Examples: `let`, `rule`, `policy`, `namespace`, `fact`, `shape`, `export`, `use`, `when`, `default`, `true`, `false`, `unknown`, `and`, `or`, `xor`, `not`, `in`, `is`, `cast`, `yield`, and type names like `string`, `number`, `list`, `map`. So you cannot name a rule `let` or a variable `when`. Use a name that is not a keyword (e.g. `myLet`, `defaultValue`). + +## Where identifiers are used + +- **Namespace FQN segments:** Each part of a fully qualified name (e.g. `com` / `example` / `auth`) must be a valid identifier. +- **Policy, rule, shape, fact, let names:** The name after the keyword must be a valid identifier (and not a keyword). +- **Shape field names, aliases (e.g. `as alias`), map dot access:** Must be valid identifiers when used in source. Map keys at runtime are strings; dot access in source (e.g. `map.key`) requires `key` to be a valid identifier. + +## Good to Know + +- Identifiers are ASCII-only: letters, digits, underscore. No Unicode letters in identifiers. +- If the lexer sees a keyword where an identifier is expected (e.g. a policy named `let`), it reports a parse error. Choose names that are not reserved. diff --git a/src/content/docs/reference/index.md b/src/content/docs/reference/index.md index 05df310..c685104 100644 --- a/src/content/docs/reference/index.md +++ b/src/content/docs/reference/index.md @@ -25,6 +25,7 @@ export shape IDENT - **[Rules](/reference/rules):** Rule syntax, when/default/body, outcome type, truthiness, cross-references. - **[Facts](/reference/facts):** Required/optional facts, type, alias, default, binding at evaluation, import binding. - **[Intermediate values (let)](/reference/let):** let syntax, scoping, immutability, type validation. +- **[Identifiers](/reference/identifiers):** What counts as a valid identifier (characters, reserved keywords). ### Types diff --git a/src/content/docs/reference/namespaces.md b/src/content/docs/reference/namespaces.md index 244e9cd..0dee126 100644 --- a/src/content/docs/reference/namespaces.md +++ b/src/content/docs/reference/namespaces.md @@ -11,7 +11,7 @@ Namespaces group [policies](/reference/policies) and [shapes](/reference/shapes) namespace fully/qualified/name ``` -The name is a **fully qualified name (FQN)**: one or more identifiers separated by forward slashes (e.g. `com/example/auth`, `mycompany/policies/security`, `app/v1`). No leading or trailing slash. Each segment must be a valid identifier. +The name is a **fully qualified name (FQN)**: one or more [identifiers](/reference/identifiers) separated by forward slashes (e.g. `com/example/auth`, `mycompany/policies/security`, `app/v1`). No leading or trailing slash. Each segment must be a valid identifier. ## Configuration & Arguments @@ -75,6 +75,6 @@ The pack contains two namespaces. Import and resolution rules determine how one Before you implement this, keep a few boundaries in mind: - **Constraint:** Only comments may appear before the namespace declaration. One namespace per file. Multiple root namespaces are allowed across the pack (in different files). -- **Naming:** Each segment of the FQN must be a valid identifier. No leading or trailing slash. Child “namespaces” (e.g. `a` vs `a/b`) are not separate language constructs; hierarchy is by convention in the FQN string. +- **Naming:** Each segment of the FQN must be a [valid identifier](/reference/identifiers). No leading or trailing slash. Child “namespaces” (e.g. `a` vs `a/b`) are not separate language constructs; hierarchy is by convention in the FQN string. - **Resolution:** When another namespace references an exported shape or imports a decision, the full namespace path (and policy name, for rules) is used by the runtime/tooling to resolve the target. See [Policy composition](/language-concepts/policy-composition) for import syntax and behavior. - **Files and namespaces:** The relationship between physical files and namespace FQNs is tooling-dependent; typically one file per namespace, but the reference does not forbid multiple files sharing the same namespace if the tooling supports it. diff --git a/src/content/docs/reference/shapes.md b/src/content/docs/reference/shapes.md index f739f3d..fbfbd8b 100644 --- a/src/content/docs/reference/shapes.md +++ b/src/content/docs/reference/shapes.md @@ -181,4 +181,4 @@ Before you implement this, keep a few boundaries in mind: - **Composition:** The composed shape effectively has all base fields plus its own. Circular composition (A with B, B with A, or longer cycles) is not allowed. Duplicate field names between base and child are not allowed. - **Export:** Only shapes declared in the same file can be exported with `export shape Name`. Exported shapes are visible to other namespaces for use as types or as base shapes in composition. Unexported shapes are namespace-local. - **Field order:** The order of fields in a shape literal does not affect type checking; only presence and types (and constraints) matter. -- **Naming:** Shape names and field names are identifiers. The same namespace can contain multiple shapes; policies in that namespace can reference any shape in the same namespace or any exported shape from another namespace (per import/resolution rules). +- **Naming:** Shape names and field names are [identifiers](/reference/identifiers). The same namespace can contain multiple shapes; policies in that namespace can reference any shape in the same namespace or any exported shape from another namespace (per import/resolution rules). diff --git a/src/content/docs/reference/types-and-values.md b/src/content/docs/reference/types-and-values.md index d6de01f..a9dc26d 100644 --- a/src/content/docs/reference/types-and-values.md +++ b/src/content/docs/reference/types-and-values.md @@ -15,7 +15,7 @@ Sentrie provides primitive types (`number`, `string`, `trinary`, `bool`, `docume **Indexing:** - List/record: `expr[number]` (zero-based index). -- Map: `expr[string]` or `expr.identifier` (key must be a string; dot form when the key is a valid identifier). +- Map: `expr[string]` or `expr.identifier` (key must be a string; dot form when the key is a [valid identifier](/reference/identifiers)). ## Configuration & Arguments @@ -34,7 +34,7 @@ Sentrie provides primitive types (`number`, `string`, `trinary`, `bool`, `docume | Type | Description | Index type | Notes | | :--- | :---------- | :--------- | :---- | | `list[T]` | Ordered sequence of values of type `T`. | `number` (zero-based). | Index out of range is an error or undefined behavior per implementation. | -| `map[T]` | Key-value map; keys are strings, values are type `T`. | `string` or `.key` (identifier). | Keys must be strings. Dot access: `map.key` when key is a valid identifier. | +| `map[T]` | Key-value map; keys are strings, values are type `T`. | `string` or `.key` (identifier). | Keys must be strings. Dot access: `map.key` when key is a [valid identifier](/reference/identifiers). | | `record[T1,T2,...]` | Fixed-length tuple with typed positions. | `number` (zero-based position). | Length and types are fixed at type declaration. | ### Cast @@ -47,7 +47,7 @@ Sentrie provides primitive types (`number`, `string`, `trinary`, `bool`, `docume - **List:** `listExpr[indexExpr]`. `indexExpr` must be a number (or evaluate to a number). Zero-based. Bounds behavior is implementation-defined (error or undefined). - **Record:** `recordExpr[indexExpr]`. Same as list; index is the position (0, 1, 2, …). -- **Map:** `mapExpr[keyExpr]` or `mapExpr.key`. Key must be a string. Dot form is equivalent to bracket form when the key is a valid identifier (e.g. `m.one` ≈ `m["one"]`). +- **Map:** `mapExpr[keyExpr]` or `mapExpr.key`. Key must be a string. Dot form is equivalent to bracket form when the key is a [valid identifier](/reference/identifiers) (e.g. `m.one` ≈ `m["one"]`). ## Where types are used @@ -91,6 +91,6 @@ let doc: document = cast { "name": "John", "age": 30 } as document Before you implement this, keep a few boundaries in mind: - **Type annotation on let:** Optional. When omitted, the value is not validated against a type. When present, the value is validated (and constraints apply); failure aborts evaluation. -- **Map keys:** Must be strings. Access with `[index]`: numeric index for list/record, string key for map. Dot access for maps only when the key is a valid identifier. +- **Map keys:** Must be strings. Access with `[index]`: numeric index for list/record, string key for map. Dot access for maps only when the key is a [valid identifier](/reference/identifiers). - **Division by zero:** Aborts evaluation. [Constraint](/reference/constraints) failure also aborts evaluation. - **Cast:** Validates against the target type and any constraints; failure aborts evaluation. Not all type combinations may be supported; see implementation for allowed conversions. From d28abea5c3d2983ad8b7642f1de5a80c27458940 Mon Sep 17 00:00:00 2001 From: Binaek Sarkar Date: Sun, 22 Feb 2026 14:56:44 +0530 Subject: [PATCH 17/21] Bring up `identifiers` docs --- astro.config.mjs | 117 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 93 insertions(+), 24 deletions(-) diff --git a/astro.config.mjs b/astro.config.mjs index 4e2d218..6f37e9f 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -26,68 +26,134 @@ export default defineConfig({ { label: "Getting Started", items: [ - { label: "Introduction & Core Philosophy", slug: "getting-started/introduction" }, + { + label: "Introduction & Core Philosophy", + slug: "getting-started/introduction", + }, { label: "Quick Start", slug: "getting-started/quick-start" }, { label: "Installation", slug: "getting-started/installation" }, - { label: "Writing your first Policy", slug: "getting-started/writing-your-first-policy" }, - { label: "Running your Policy", slug: "getting-started/running-your-policy" }, + { + label: "Writing your first Policy", + slug: "getting-started/writing-your-first-policy", + }, + { + label: "Running your Policy", + slug: "getting-started/running-your-policy", + }, { label: "Enforcement", slug: "getting-started/enforcement" }, ], }, { label: "Language Concepts", items: [ - { label: "Type System & Shapes Overview", slug: "language-concepts/type-system-shapes" }, - { label: "Policy Composition", slug: "language-concepts/policy-composition" }, - { label: "Pattern Matching & Conditionals", slug: "language-concepts/pattern-matching-conditionals" }, + { + label: "Type System & Shapes Overview", + slug: "language-concepts/type-system-shapes", + }, + { + label: "Policy Composition", + slug: "language-concepts/policy-composition", + }, + { + label: "Pattern Matching & Conditionals", + slug: "language-concepts/pattern-matching-conditionals", + }, ], }, { label: "Language Reference", items: [ { label: "Overview", slug: "reference" }, + { label: "Identifiers", slug: "reference/identifiers" }, { label: "Namespaces", slug: "reference/namespaces" }, { label: "Policies", slug: "reference/policies" }, { label: "Rules", slug: "reference/rules" }, { label: "Facts", slug: "reference/facts" }, { label: "Intermediate Values (let)", slug: "reference/let" }, - { label: "Identifiers", slug: "reference/identifiers" }, { label: "Types and Values", slug: "reference/types-and-values" }, { label: "Constraints", slug: "reference/constraints" }, { label: "Trinary Values", slug: "reference/trinary" }, { label: "Shapes", slug: "reference/shapes" }, - { label: "Arithmetic Operations", slug: "reference/arithmetic-operations" }, - { label: "Boolean Operations", slug: "reference/boolean-operations" }, - { label: "Membership Operations", slug: "reference/membership-operations" }, - { label: "Collection Operations", slug: "reference/collection-operations" }, + { + label: "Arithmetic Operations", + slug: "reference/arithmetic-operations", + }, + { + label: "Boolean Operations", + slug: "reference/boolean-operations", + }, + { + label: "Membership Operations", + slug: "reference/membership-operations", + }, + { + label: "Collection Operations", + slug: "reference/collection-operations", + }, { label: "Functions", slug: "reference/functions" }, { label: "Precedence", slug: "reference/precedence" }, - { label: "Security and Permissions", slug: "reference/security-and-permissions" }, + { + label: "Security and Permissions", + slug: "reference/security-and-permissions", + }, ], }, { label: "TypeScript Modules", items: [ { label: "Overview", slug: "reference/typescript_modules" }, - { label: "JavaScript Globals", slug: "reference/typescript_modules/sentrie/js" }, - { label: "Collection", slug: "reference/typescript_modules/sentrie/collection" }, - { label: "Crypto", slug: "reference/typescript_modules/sentrie/crypto" }, - { label: "Encoding", slug: "reference/typescript_modules/sentrie/encoding" }, - { label: "Hash", slug: "reference/typescript_modules/sentrie/hash" }, - { label: "JSON", slug: "reference/typescript_modules/sentrie/json" }, + { + label: "JavaScript Globals", + slug: "reference/typescript_modules/sentrie/js", + }, + { + label: "Collection", + slug: "reference/typescript_modules/sentrie/collection", + }, + { + label: "Crypto", + slug: "reference/typescript_modules/sentrie/crypto", + }, + { + label: "Encoding", + slug: "reference/typescript_modules/sentrie/encoding", + }, + { + label: "Hash", + slug: "reference/typescript_modules/sentrie/hash", + }, + { + label: "JSON", + slug: "reference/typescript_modules/sentrie/json", + }, { label: "JWT", slug: "reference/typescript_modules/sentrie/jwt" }, { label: "Net", slug: "reference/typescript_modules/sentrie/net" }, - { label: "Regex", slug: "reference/typescript_modules/sentrie/regex" }, - { label: "Semver", slug: "reference/typescript_modules/sentrie/semver" }, - { label: "Time", slug: "reference/typescript_modules/sentrie/time" }, + { + label: "Regex", + slug: "reference/typescript_modules/sentrie/regex", + }, + { + label: "Semver", + slug: "reference/typescript_modules/sentrie/semver", + }, + { + label: "Time", + slug: "reference/typescript_modules/sentrie/time", + }, { label: "URL", slug: "reference/typescript_modules/sentrie/url" }, - { label: "UUID", slug: "reference/typescript_modules/sentrie/uuid" }, + { + label: "UUID", + slug: "reference/typescript_modules/sentrie/uuid", + }, ], }, { label: "Extensibility", items: [ - { label: "Writing Custom TypeScript Modules", slug: "extensibility/writing-custom-typescript-modules" }, + { + label: "Writing Custom TypeScript Modules", + slug: "extensibility/writing-custom-typescript-modules", + }, ], }, { @@ -103,7 +169,10 @@ export default defineConfig({ { label: "Deployment & Operations", items: [ - { label: "Running as a Service", slug: "deployment-operations/running-as-service" }, + { + label: "Running as a Service", + slug: "deployment-operations/running-as-service", + }, ], }, ], From 7aec6650a15eeae295b25908b765b8c22a371b5b Mon Sep 17 00:00:00 2001 From: Binaek Sarkar Date: Sun, 22 Feb 2026 15:08:50 +0530 Subject: [PATCH 18/21] Clarify constraints documentation regarding Infinity and NaN handling, ensuring accurate representation of behavior in the language. --- src/content/docs/reference/constraints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/reference/constraints.md b/src/content/docs/reference/constraints.md index d6e9559..11ea856 100644 --- a/src/content/docs/reference/constraints.md +++ b/src/content/docs/reference/constraints.md @@ -171,6 +171,6 @@ Before you implement this, keep a few boundaries in mind: - Order of application is defined by the runtime. All specified constraints must pass; failure aborts evaluation immediately. - Use shapes to reuse constrained types (e.g. `shape ID string @uuid()`) and to name element types in collections. - Numeric constraints use float64; `@even` and `@odd` use modulo. `@multiple_of` uses a small epsilon for floating-point remainder checks. -- **Infinity and NaN:** The language has no infinity or NaN literal, and division by zero aborts (it does not produce ±∞). So `@infinite()` and `@nan()` only apply when a number comes from TypeScript or external input (e.g. a JS function returning `Infinity`). +- **Infinity and NaN:** The language has no infinity or NaN literal, and division by zero aborts (it does not produce `±∞`). So `@infinite()` and `@nan()` only apply when a number comes from TypeScript or external input (e.g. a JS function returning `Infinity`). - String `@regexp` uses Go’s [regexp](https://pkg.go.dev/regexp) semantics. Invalid pattern strings cause an evaluation error. - Map types currently have no built-in constraints; only list and element types (e.g. `list[T]` with constrained `T`) are covered above. From 4b86b326af2c9a0df10f98da99f1eadb5e1ba628 Mon Sep 17 00:00:00 2001 From: Binaek Sarkar Date: Sun, 22 Feb 2026 15:30:50 +0530 Subject: [PATCH 19/21] Enhance collection operations documentation by adding examples for `any`, `all`, `count`, `distinct`, `reduce`, and `filter`. Clarify usage of loop variables and recursive value comparison in membership operations for maps. --- .../docs/reference/collection-operations.md | 34 +++++++++++++++---- .../docs/reference/membership-operations.md | 4 +-- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/content/docs/reference/collection-operations.md b/src/content/docs/reference/collection-operations.md index a4411e9..bffc1c7 100644 --- a/src/content/docs/reference/collection-operations.md +++ b/src/content/docs/reference/collection-operations.md @@ -9,15 +9,34 @@ Collection operations apply to lists and maps. They are declarative: they return ```text any collection as element, index { yield trinary } +``` + +```text all collection as element, index { yield trinary } +``` + +```text +count collection +``` + +```text +distinct collection as left, right { yield trinary } +``` + +```text +reduce collection from initial as acc, element, index { yield expr } +``` + +```text filter collection as element, index { yield trinary } +``` + +```text map collection as element, index { yield expr } -reduce collection from initial as acc, element, index { yield expr } -count collection -distinct collection ``` -- **element, index:** Loop variables. For lists, element is the item and index is the position (0-based). For maps, element and index semantics are implementation-defined (e.g. key-value pair or value and key). The index parameter may be optional in some forms. +- **element, index:** Loop variables (for `any`, `all`, `filter`, `map`, `reduce`). For lists, element is the item and index is the position (0-based). For maps, element and index semantics are implementation-defined (e.g. key-value pair or value and key). The index parameter may be optional in some forms. +- **left, right:** For `distinct`, two iterator names. During evaluation, `left` is bound to an item already in the distinct result so far, and `right` is the candidate element. The block must yield a trinary/boolean: truthy means “left and right are equal” (candidate is a duplicate and is dropped), falsy means “different” (candidate is added to the result). Typically you yield `left == right` or a custom equality. - **acc, element, index:** For `reduce`, `acc` is the accumulator (initial value on first iteration, then the previous `yield` result), `element` is the current element, and `index` is the position/key as above. ## Configuration & Arguments @@ -30,13 +49,14 @@ distinct collection | `map` | collection | list | New list whose elements are the yielded values (one per element). Type of each yield can be any type. | | `reduce` | collection, initial | type of initial | Fold: start with `acc = initial`; for each element, set `acc` to the yielded expression. Final `acc` is the result. Empty collection → returns initial. | | `count` | collection | number | Number of elements in the collection. Empty → 0. | -| `distinct` | collection | same type | New collection with duplicate elements removed. Equality for deduplication is by the language’s `==`. | +| `distinct` | collection | same type | New list with duplicates removed. Takes two iterator names (`left`, `right`) and a predicate block. For each candidate (`right`), the block is evaluated with each already-selected item (`left`); if any evaluation is truthy, the candidate is treated as a duplicate. Typically the block yields `left == right` or a custom equality. **Input must be a list** (maps are not supported). | **Returns:** As in the table. For `any`/`all`, the result is the trinary/boolean produced by the predicate. For `filter`/`map`/`distinct`, the result is a new collection (or list for `map`). For `reduce`, the result is the final accumulator. For `count`, the result is a number. ## Block and yield rules - **any, all, filter:** The block must yield a value that is interpreted as truthy or falsy (trinary/boolean). One yield per iteration. The block is evaluated once per element. +- **distinct:** The block must yield a trinary/boolean (predicate). It is evaluated for each pair (already-selected item as `left`, candidate item as `right`). Truthy means “equal” (candidate is a duplicate); falsy means “different” (candidate is kept). Typically `yield left == right`. - **map:** The block must yield an expression per iteration. The type of the yielded value can be any type; the result is a list of those values. - **reduce:** The block must yield an expression that becomes the next accumulator. First iteration: `acc` is `initial`. Subsequent iterations: `acc` is the previous yield. The final yield (after the last element) is the result of `reduce`. @@ -63,7 +83,7 @@ let evens: list[number] = filter numbers as num, idx { yield num % 2 == 0 } let doubled: list[number] = map numbers as num, idx { yield num * 2 } let sum: number = reduce numbers from 0 as acc, num, idx { yield acc + num } let n: number = count numbers -let uniq: list[number] = distinct numbers +let uniq: list[number] = distinct numbers as left, right { yield left == right } ``` ### reduce with initial and multiple uses @@ -78,6 +98,6 @@ let avg: number = sum / count scores Before you implement this, keep a few boundaries in mind: - **Applicability:** Only valid on collections (lists, maps). The original collection is not modified. -- **Block:** Must yield once per iteration. For `any`/`all`/`filter` the yield is a predicate (trinary/boolean); for `map`/`reduce` the yield is an expression that becomes the new value or accumulator. +- **Block:** Must yield once per iteration (or per comparison for `distinct`). For `any`/`all`/`filter`/`distinct` the yield is a predicate (trinary/boolean); for `map`/`reduce` the yield is an expression that becomes the new value or accumulator. **distinct** accepts only lists; the block receives `left` (item already in result) and `right` (candidate), e.g. `distinct items as left, right { yield left == right }`. - **Empty collection:** `any` → false, `all` → true, `filter`/`map`/`distinct` → empty result, `count` → 0, `reduce` → initial. - **Reduce:** First iteration uses `initial` as `acc`; each subsequent iteration uses the previous `yield` result as `acc`. diff --git a/src/content/docs/reference/membership-operations.md b/src/content/docs/reference/membership-operations.md index d94def0..74445a0 100644 --- a/src/content/docs/reference/membership-operations.md +++ b/src/content/docs/reference/membership-operations.md @@ -44,7 +44,7 @@ haystack contains needle ### Map — subset - **Haystack:** map. **Needle:** map. -- **Result:** true if every key in needle exists in haystack and the corresponding values are equal (by `==`). Extra keys in haystack are allowed. If any key in needle is missing from haystack or any value differs, result is false. +- **Result:** true if every key in needle exists in haystack and the corresponding values are equal (by `==`). Extra keys in haystack are allowed. If any key in needle is missing from haystack or any value differs, result is false. The value comparison is **recursive**: when values are themselves maps or lists, they are compared recursively (nested maps must match key-for-key; nested lists element-for-element). ## Examples in Action @@ -70,5 +70,5 @@ Before you implement this, keep a few boundaries in mind: - **Strings:** Needle must be non-empty for a true result; empty needle → false. Empty string haystack → false (unless needle is also empty; then typically false by “non-empty substring” rule). - **Lists:** Element-wise equality; no deep comparison beyond the language’s `==`. Empty list → false. - **Maps (key):** Needle string checks key presence only. Map keys are strings. -- **Maps (subset):** Needle map: every key in needle must exist in haystack with the same value; extra keys in haystack are allowed. Type mismatch (e.g. haystack not a map, or needle not string/map) → false. +- **Maps (subset):** Needle map: every key in needle must exist in haystack with the same value; extra keys in haystack are allowed. Value equality is recursive (nested maps/lists are compared recursively). Type mismatch (e.g. haystack not a map, or needle not string/map) → false. - **Regex:** For pattern matching on strings, use [matches](/reference/boolean-operations) in Boolean Operations. From c1de72d637cbc2ac98fd0c09daa1c7137e3b433e Mon Sep 17 00:00:00 2001 From: Binaek Sarkar Date: Wed, 4 Mar 2026 18:18:50 +0530 Subject: [PATCH 20/21] Add troubleshooting and guide docs --- astro.config.mjs | 22 +++++ src/content/docs/getting-started.md | 11 +-- .../docs/guides/deployment-and-security.md | 56 ++++++++++++ .../guides/policy-design-best-practices.md | 53 ++++++++++++ src/content/docs/guides/testing-policies.md | 85 +++++++++++++++++++ src/content/docs/reference/errors.md | 66 ++++++++++++++ .../docs/troubleshooting/common-errors.md | 44 ++++++++++ src/content/docs/troubleshooting/index.md | 15 ++++ 8 files changed, 345 insertions(+), 7 deletions(-) create mode 100644 src/content/docs/guides/deployment-and-security.md create mode 100644 src/content/docs/guides/policy-design-best-practices.md create mode 100644 src/content/docs/guides/testing-policies.md create mode 100644 src/content/docs/reference/errors.md create mode 100644 src/content/docs/troubleshooting/common-errors.md create mode 100644 src/content/docs/troubleshooting/index.md diff --git a/astro.config.mjs b/astro.config.mjs index 6f37e9f..28fe775 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -43,6 +43,20 @@ export default defineConfig({ { label: "Enforcement", slug: "getting-started/enforcement" }, ], }, + { + label: "Guides", + items: [ + { label: "Testing Policies", slug: "guides/testing-policies" }, + { + label: "Policy Design Best Practices", + slug: "guides/policy-design-best-practices", + }, + { + label: "Deployment and Security", + slug: "guides/deployment-and-security", + }, + ], + }, { label: "Language Concepts", items: [ @@ -92,6 +106,7 @@ export default defineConfig({ }, { label: "Functions", slug: "reference/functions" }, { label: "Precedence", slug: "reference/precedence" }, + { label: "Errors", slug: "reference/errors" }, { label: "Security and Permissions", slug: "reference/security-and-permissions", @@ -166,6 +181,13 @@ export default defineConfig({ { label: "sentrie validate", slug: "cli-reference/validate" }, ], }, + { + label: "Troubleshooting", + items: [ + { label: "Overview", slug: "troubleshooting" }, + { label: "Common Errors", slug: "troubleshooting/common-errors" }, + ], + }, { label: "Deployment & Operations", items: [ diff --git a/src/content/docs/getting-started.md b/src/content/docs/getting-started.md index 20d8e34..2a7a509 100644 --- a/src/content/docs/getting-started.md +++ b/src/content/docs/getting-started.md @@ -204,15 +204,12 @@ policy pricing { ## Troubleshooting -### Common Issues +For a catalog of common problems and fixes, see: -**Policy not found**: Make sure your namespace and policy names match exactly in the URL path. +- [Troubleshooting Overview](/troubleshooting) +- [Common Errors](/troubleshooting/common-errors) -**Invalid JSON**: Ensure your request body is valid JSON and matches the expected structure. - -**Server won't start**: Check that port 7529 is available, or use `--port` to specify a different port. - -### Getting Help +You can also: - Check the [Language Reference](/reference) for syntax help - Look at the [CLI Reference](/cli-reference) for command options diff --git a/src/content/docs/guides/deployment-and-security.md b/src/content/docs/guides/deployment-and-security.md new file mode 100644 index 0000000..ca732f1 --- /dev/null +++ b/src/content/docs/guides/deployment-and-security.md @@ -0,0 +1,56 @@ +--- +title: Deployment and Security +description: Running Sentrie safely in production: HTTP service, network exposure, and pack permissions. +--- + +Sentrie is a deterministic policy engine that evaluates rules and returns decisions. In production you typically run it as a separate service and call it over HTTP or from automation. This guide covers security-focused aspects of that setup. + +## Serving Sentrie in production + +Use `sentrie serve` to start the HTTP API: + +```bash +sentrie serve --pack-location /etc/sentrie/policies --listen 0.0.0.0 --port 8080 +``` + +Key points: + +- Sentrie serves plain HTTP; terminate TLS in a reverse proxy (e.g. nginx, Envoy) or API gateway. +- Add authentication and authorization at the proxy/gateway layer before requests reach Sentrie. +- Restrict which networks can reach the Sentrie service (firewalls, security groups, service mesh). + +See: + +- [CLI: `sentrie serve`](/cli-reference/serve) +- [Running as a Service](/deployment-operations/running-as-service) + +## Pack permissions (`fs_read`, `net`, `env`) + +Policy packs declare what they are allowed to reach at runtime in `sentrie.pack.toml`: + +```toml +[permissions] +fs_read = ["./data/**"] +net = ["https://api.example.com"] +env = ["APP_ENV", "API_KEY"] +``` + +Recommendations: + +- **Principle of least privilege**: Only grant the filesystem paths, hosts, and environment variables a pack truly needs. +- **Review permissions for each release**: Treat permission changes like code changes and review them in PRs. +- **Keep secrets in env, not facts**: Let Sentrie read secrets from permitted environment variables instead of embedding them in facts. + +See: [Security and Permissions](/reference/security-and-permissions) + +## Hardening the HTTP surface + +Combine Sentrie with your existing platform primitives: + +- **TLS and mTLS**: Terminate HTTPS at a reverse proxy; optionally enforce mTLS between callers and the proxy. +- **AuthN/AuthZ for callers**: Require auth tokens for access to the Sentrie endpoint and restrict which services can call which decisions. +- **Rate limiting and quotas**: Apply rate limits and timeouts at the proxy; treat Sentrie like any other critical backend. +- **Observability**: Export logs and metrics (through your wrapper or sidecar) so you can monitor error rates and latency. + +Because evaluation is deterministic and side-effect free, you can safely replay requests in non-production environments for debugging and audits. + diff --git a/src/content/docs/guides/policy-design-best-practices.md b/src/content/docs/guides/policy-design-best-practices.md new file mode 100644 index 0000000..87d27de --- /dev/null +++ b/src/content/docs/guides/policy-design-best-practices.md @@ -0,0 +1,53 @@ +--- +title: Policy Design Best Practices +description: Practical guidance for naming, structuring, and composing Sentrie policies. +--- + +This page collects practical advice for designing policies that stay readable and maintainable as your system grows. + +## Namespaces, policies, and rules + +- **Use namespaces for domains**: e.g. `com/example/auth`, `com/example/billing`. Keep related policies in the same namespace. +- **Name policies after subjects**: e.g. `userAccess`, `documentAccess`, `pricing`, `orders`. +- **Name rules after decisions**: e.g. `allow`, `canRead`, `calculatePrice`, `isAdmin`. + +This makes CLI and HTTP targets (`namespace/policy/rule`) self-explanatory. + +See: [Namespaces](/reference/namespaces), [Policies](/reference/policies), [Rules](/reference/rules) + +## Designing facts + +- **Model real inputs**: Facts should mirror the data your callers already have (e.g. `user`, `request`, `resource`, `context`). +- **Prefer shapes over `document`**: Use shapes and field modifiers (`!`, `?`, `!?`) to describe required vs optional data precisely. It improves validation and error messages. See [Shapes](/reference/shapes). +- **Use optional facts sparingly**: Make a fact optional (`fact name?`) only when callers truly may omit it. Provide a `default` where it simplifies callers. + +See: [Facts](/reference/facts) + +## When to compose vs inline + +Sentrie’s export/import model lets you share rules across policies. + +- **Extract shared logic**: Put cross-cutting checks (e.g. “is admin”, “is active user”) into a dedicated policy and export just those rules. +- **Import rather than duplicate**: Use `import decision of ... from ... with ...` instead of copying conditions into every policy. See [Policy Composition](/language-concepts/policy-composition) and [Exporting & Importing Rules](/reference/exporting-and-importing-rules). +- **Avoid deeply nested imports**: Prefer a small number of well-known base policies over long chains of imports that are hard to follow. + +## Using constraints and shapes effectively + +- **Capture validation in types**: Move repeated checks (UUIDs, email addresses, ranges) into constrained shapes like `shape ID string @uuid()` or `shape Percent number @min(0) @max(100)`. +- **Constrain collections at the element type**: e.g. `list[Permission]` where `Permission` is a constrained string shape. +- **Fail early**: Let constraint failures abort evaluation rather than encoding “invalid input” conditions in rules. + +See: [Constraints](/reference/constraints), [Types and Values](/reference/types-and-values) + +## Integrating TypeScript modules + +- **Keep policies declarative**: Use TypeScript for low-level helpers (parsing, normalization, complex calculations), and keep decision logic in Sentrie. +- **Use modules consistently**: Group helpers into a small set of modules (e.g. `utils/time.ts`, `auth/claims.ts`) and import them with clear aliases. +- **Prefer built-ins when available**: Use `@sentrie/*` modules first before adding custom equivalents. See [Using TypeScript](/reference/using-typescript) and the [TypeScript modules reference](/reference/typescript_modules). + +## Testing and versioning + +- **Test policies like code**: Use `sentrie exec` and `sentrie validate` in CI to catch regressions when policies change. See [Testing Policies](/guides/testing-policies). +- **Version packs**: Use the `version` field in `sentrie.pack.toml` to manage releases of your policy pack and coordinate with callers. See [`sentrie.pack.toml`](/structure-of-a-policy-pack/packfile). +- **Document contracts**: In comments near policies, describe what facts are required and what decisions mean so consumers can rely on them. + diff --git a/src/content/docs/guides/testing-policies.md b/src/content/docs/guides/testing-policies.md new file mode 100644 index 0000000..113a227 --- /dev/null +++ b/src/content/docs/guides/testing-policies.md @@ -0,0 +1,85 @@ +--- +title: Testing Policies +description: How to test Sentrie policies locally and in CI using the CLI. +--- + +Good tests make it safe to evolve policies. Sentrie’s CLI is designed to be scriptable, so you can run the same policy both during development and in CI. + +## When to use which command + +- **`sentrie exec`**: Execute rules and see decisions. Use for local development, debugging, and “golden” tests that assert specific outputs. +- **`sentrie validate`**: Load the pack and type-check policies (and optional facts) without executing rules. Use to catch syntax, type, and pack errors early—especially in CI. + +See: + +- [CLI: `sentrie exec`](/cli-reference/exec) +- [CLI: `sentrie validate`](/cli-reference/validate) + +## Quick local testing with `sentrie exec` + +Run a single exported rule with inline facts: + +```bash +sentrie exec com/example/auth/access/allow \ + --facts '{"user":{"role":"admin","status":"active"}}' +``` + +Or evaluate all exported rules in a policy: + +```bash +sentrie exec com/example/auth/access \ + --facts '{"user":{"role":"user","status":"active"}}' +``` + +Tips: + +- Use `--output json` when you want to parse results in scripts. +- Use `--fact-file` to keep larger fact payloads in separate JSON files and override specific keys with `--facts`. + +## Structuring tests with fact files + +For repeatable tests, keep fact files alongside your pack: + +```text +policy-pack/ + sentrie.pack.toml + policies/ + auth.sentrie + facts/ + admin.json + user.json +``` + +Example: + +```bash +sentrie exec com/example/auth/access/allow \ + --pack-location ./policy-pack \ + --fact-file ./policy-pack/facts/admin.json \ + --output json +``` + +Use a simple shell script or Makefile target to run a suite of such commands. + +## Using `sentrie validate` in CI + +Add a validation step to your CI pipeline to catch errors before deploy: + +```bash +# Validate the whole pack from the repo root +sentrie validate com/example/auth/access --pack-location ./policy-pack +``` + +You can optionally pass representative facts to catch shape and constraint issues: + +```bash +sentrie validate com/example/auth/access \ + --pack-location ./policy-pack \ + --facts '{"user":{"role":"admin","status":"active"}}' +``` + +Recommended pattern: + +1. Run `sentrie validate` on the main policies you care about. +2. Run a small set of `sentrie exec ... --output json` commands and assert on the JSON in your CI language of choice. + diff --git a/src/content/docs/reference/errors.md b/src/content/docs/reference/errors.md new file mode 100644 index 0000000..d4fb820 --- /dev/null +++ b/src/content/docs/reference/errors.md @@ -0,0 +1,66 @@ +--- +title: Error Reference +description: Overview of Sentrie error categories, CLI exit codes, and HTTP problem responses. +--- + +This page summarizes how Sentrie reports errors across the CLI, pack loading, evaluation, and the HTTP service. + +## Error categories + +- **Parse and pack errors**: Syntax issues in `*.sentrie` files or invalid `sentrie.pack.toml`. +- **Type and constraint errors**: Type mismatches, missing required facts, and constraint violations. +- **Evaluation errors**: Failures while evaluating a rule (often surfaced as type or constraint errors at a specific expression). +- **Service/API errors**: HTTP 4xx/5xx responses when using `sentrie serve`. + +## CLI exit codes + +All CLI commands follow a simple convention: + +- Exit code **0**: success. +- Non-zero: failure (message written to stderr). + +Common cases: + +- `sentrie exec`: non-zero on pack load failure, invalid target, missing required facts, or evaluation error. +- `sentrie validate`: non-zero on pack load failure, parse or type error, or invalid facts passed for validation. +- `sentrie serve`: non-zero on startup if the pack cannot be loaded or the port/listen configuration is invalid. + +See: + +- [CLI Reference](/cli-reference) +- [sentrie exec](/cli-reference/exec) +- [sentrie validate](/cli-reference/validate) +- [sentrie serve](/cli-reference/serve) + +## HTTP problem details + +When running as a service, Sentrie uses [RFC 9457 problem+json](https://www.rfc-editor.org/rfc/rfc9457) for error responses: + +```json +{ + "type": "https://sentrie.sh/problems/", + "title": "string", + "status": 400, + "detail": "string", + "instance": "string" +} +``` + +Typical status codes: + +| Status | Meaning | +| :----- | :------ | +| `400` | Bad request (invalid JSON, missing or malformed path, bad `facts` shape). | +| `404` | Policy, namespace, or rule not found. | +| `405` | Method not allowed (e.g. non-POST on `/decision`). | +| `500` | Internal error during evaluation. | + +See: [Running as a Service](/deployment-operations/running-as-service) + +## Relationship to troubleshooting + +Use this page as a high-level map of how Sentrie reports errors. For a task-oriented list of frequent issues, concrete messages, and fixes, see: + +- [Troubleshooting Overview](/troubleshooting) +- [Common Errors](/troubleshooting/common-errors) + diff --git a/src/content/docs/troubleshooting/common-errors.md b/src/content/docs/troubleshooting/common-errors.md new file mode 100644 index 0000000..9250b6a --- /dev/null +++ b/src/content/docs/troubleshooting/common-errors.md @@ -0,0 +1,44 @@ +--- +title: Common Errors +description: The most frequent Sentrie errors, why they happen, and how to fix them. +--- + +This page lists common errors you may encounter when authoring or running Sentrie policies, along with likely causes and suggested fixes. + +## Language and pack errors + +| Error / symptom | When it happens | How to fix | See also | +| :-------------- | :-------------- | :--------- | :------- | +| Parse error (unexpected token, invalid syntax) | While loading `*.sentrie` files (CLI or server startup) | Check for missing `namespace`, unmatched braces, or misplaced keywords; compare against the examples in the reference pages. | [Namespaces](/reference/namespaces), [Policies](/reference/policies), [Rules](/reference/rules) | +| Unknown namespace/policy/rule | `sentrie exec` / `sentrie validate` / HTTP `/decision/...` target does not match an exported rule | Verify the namespace, policy, and rule names in your files and commands; ensure the rule is exported with `export decision of ...`. | [CLI: exec](/cli-reference/exec), [Running as a Service](/deployment-operations/running-as-service), [Exporting & Importing Rules](/reference/exporting-and-importing-rules) | +| Pack file (`sentrie.pack.toml`) error | Pack cannot be loaded; invalid or missing schema/pack section | Ensure `[schema]` and `[pack]` sections exist, `schema.version = 1`, and `pack.name`/`pack.version` are valid. | [`sentrie.pack.toml`](/structure-of-a-policy-pack/packfile) | + +## Facts, types, and constraints + +| Error / symptom | When it happens | How to fix | See also | +| :-------------- | :-------------- | :--------- | :------- | +| Missing required fact / `ErrRequiredFact` | Evaluation runs without providing all required facts | Add the missing fact to `--facts`, `--fact-file`, or the HTTP request body; or make the fact optional (`fact name?`) if it is truly optional. | [Facts](/reference/facts), [CLI: exec](/cli-reference/exec) | +| Type mismatch for fact | Fact value does not match its declared type or shape | Check the JSON structure and field names; ensure it matches the shape and field modifiers (`!`, `?`) defined in the policy. | [Types and Values](/reference/types-and-values), [Shapes](/reference/shapes) | +| Constraint validation failed | A value violates a constraint like `@min(0)` or `@uuid()` | Adjust the input value or change the constraint to match what you actually allow; avoid casting into constrained types unless the value really satisfies them. | [Constraints](/reference/constraints) | + +## Execution and HTTP service + +| Error / symptom | When it happens | How to fix | See also | +| :-------------- | :-------------- | :--------- | :------- | +| HTTP 400 (Bad Request) | Malformed JSON, missing `facts` field, or invalid path | Validate the JSON body, ensure it has a top-level `"facts"` object, and double-check the `/decision/{namespace}/{policy}[/{rule}]` path. | [Running as a Service](/deployment-operations/running-as-service) | +| HTTP 404 (Not Found) | Namespace, policy, or rule path does not resolve | Confirm that the namespace/policy/rule exists and that the rule is exported; check for typos and casing differences. | [Running as a Service](/deployment-operations/running-as-service), [Exporting & Importing Rules](/reference/exporting-and-importing-rules) | +| HTTP 405 (Method Not Allowed) | Using GET/PUT/etc. against the decision endpoint | Use `POST` for `/decision/...` and `GET` for `/health`. | [Running as a Service](/deployment-operations/running-as-service) | +| HTTP 500 (Internal Server Error) | Runtime failure during evaluation (e.g. type or constraint error) | Inspect the error detail, then cross-check the relevant policy, facts, and constraints; run the same target via `sentrie exec` or `sentrie validate` for more context. | [CLI: exec](/cli-reference/exec), [CLI: validate](/cli-reference/validate) | + +## Permissions and environment + +If your pack uses TypeScript modules that read files, call the network, or access environment variables, you may see permission-related failures: + +- Files or directories not readable when using local I/O helpers. +- Network calls failing despite valid URLs. +- Environment variables always appearing unset. + +These are usually caused by restrictive pack permissions. + +See **[Security and Permissions](/reference/security-and-permissions)** for how to configure `fs_read`, `net`, and `env` in `sentrie.pack.toml` and apply the principle of least privilege. + diff --git a/src/content/docs/troubleshooting/index.md b/src/content/docs/troubleshooting/index.md new file mode 100644 index 0000000..04ec98c --- /dev/null +++ b/src/content/docs/troubleshooting/index.md @@ -0,0 +1,15 @@ +--- +title: Troubleshooting +description: Diagnose and fix common issues when developing or running Sentrie policies. +--- + +Sentrie is designed to be deterministic and predictable, but misconfigurations and type or pack issues can still cause errors. This section helps you quickly identify what went wrong and where to look next. + +## What you'll find here + +- **Common errors**: Typical parse, type, and runtime failures and how to fix them. +- **Pointers to reference docs**: Where to look in the language and CLI reference for deeper details. +- **Operational hints**: When a problem is in your pack, your CLI invocation, or the HTTP service. + +Start with **[Common Errors](/troubleshooting/common-errors)** for a quick lookup of error messages, causes, and fixes. + From 53f0f586ebb4e452c39533ee1ae7095e5aedd64a Mon Sep 17 00:00:00 2001 From: Binaek Sarkar Date: Wed, 4 Mar 2026 18:19:04 +0530 Subject: [PATCH 21/21] Refine reference and service docs --- src/content/docs/quick-glance.mdx | 4 +- .../exporting-and-importing-rules.md | 2 +- src/content/docs/reference/identifiers.md | 46 +++- src/content/docs/reference/policies.md | 2 +- .../typescript_modules/sentrie/hash.md | 4 +- .../docs/running-sentrie/serving-policies.md | 234 +----------------- 6 files changed, 59 insertions(+), 233 deletions(-) diff --git a/src/content/docs/quick-glance.mdx b/src/content/docs/quick-glance.mdx index bc19f41..885fbf2 100644 --- a/src/content/docs/quick-glance.mdx +++ b/src/content/docs/quick-glance.mdx @@ -53,8 +53,8 @@ Sentrie is a powerful, flexible policy enforcement engine designed for modern ap Get up and running with Sentrie in minutes: ```bash -# Install Sentrie -go install github.com/sentrie-sh/sentrie@latest +# Install Sentrie (macOS, Linux, WSL2) +curl -fsSL https://sentrie.sh/install.sh | bash # Create your first policy mkdir my-policy && cd my-policy diff --git a/src/content/docs/reference/exporting-and-importing-rules.md b/src/content/docs/reference/exporting-and-importing-rules.md index 6590efb..668c904 100644 --- a/src/content/docs/reference/exporting-and-importing-rules.md +++ b/src/content/docs/reference/exporting-and-importing-rules.md @@ -4,7 +4,7 @@ description: "Learn how to export rules from policies and import them into other --- -Sentrie allows you to export rules from one policy and import them into another, enabling code reuse and policy composition. This guide covers how to export rules, import them, and work with attachments. +Sentrie allows you to export rules from one policy and import them into another, enabling code reuse and policy composition. This guide focuses on patterns, best practices, and troubleshooting. For the core syntax and evaluation semantics of export/import, see [Policy Composition](/language-concepts/policy-composition). ## Why Export and Import? diff --git a/src/content/docs/reference/identifiers.md b/src/content/docs/reference/identifiers.md index f3529de..ee851cb 100644 --- a/src/content/docs/reference/identifiers.md +++ b/src/content/docs/reference/identifiers.md @@ -26,7 +26,51 @@ So: only ASCII letters, digits, and underscores. No spaces, hyphens, or other ch ## Reserved keywords -Language keywords are reserved and cannot be used as identifiers. Examples: `let`, `rule`, `policy`, `namespace`, `fact`, `shape`, `export`, `use`, `when`, `default`, `true`, `false`, `unknown`, `and`, `or`, `xor`, `not`, `in`, `is`, `cast`, `yield`, and type names like `string`, `number`, `list`, `map`. So you cannot name a rule `let` or a variable `when`. Use a name that is not a keyword (e.g. `myLet`, `defaultValue`). +Language keywords are reserved and cannot be used as identifiers. Using them as names (for rules, facts, shapes, etc.) will result in parse errors. + +### Core keywords + +| Keyword | Notes | +| :------ | :---- | +| `namespace` | Namespace declaration. | +| `policy` | Policy declaration. | +| `shape` | Shape declaration. | +| `fact` | Fact declaration. | +| `rule` | Rule declaration. | +| `export` | Exporting decisions or shapes. | +| `use` | Importing TypeScript modules. | +| `let` | Intermediate values. | +| `when` | Rule guard. | +| `default` | Default value for rules. | +| `yield` | Value returned from a rule body. | + +### Literals and logical operators + +| Keyword | Notes | +| :------ | :---- | +| `true` | Boolean literal. | +| `false` | Boolean literal. | +| `unknown` | Trinary literal. | +| `and` | Logical AND. | +| `or` | Logical OR. | +| `xor` | Logical XOR. | +| `not` | Logical NOT. | +| `in` | Membership operator. | +| `is` / `is not` | Type and existence checks. | + +### Built-in types + +| Keyword | Notes | +| :------ | :---- | +| `string` | String type. | +| `number` | Numeric type. | +| `bool` | Boolean type. | +| `trinary` | Three-valued logic type. | +| `document` | Unstructured JSON-like value. | +| `list` | List collection type. | +| `map` | Map collection type. | + +Do not use any of these as identifiers. For example, avoid naming a rule `let` or a fact `default`. Prefer descriptive names such as `userRule`, `defaultDecision`, or `currentUser` instead. ## Where identifiers are used diff --git a/src/content/docs/reference/policies.md b/src/content/docs/reference/policies.md index 6152305..e7248e5 100644 --- a/src/content/docs/reference/policies.md +++ b/src/content/docs/reference/policies.md @@ -57,9 +57,9 @@ policy userAccess { ```sentrie policy userAccess { - use { sha256 } from @sentrie/hash fact user: User as currentUser fact context?: Context as ctx default {} + use { sha256 } from @sentrie/hash let adminRoles = ["admin", "super_admin"] rule canRead = default false when currentUser.role is defined { yield currentUser.role in adminRoles diff --git a/src/content/docs/reference/typescript_modules/sentrie/hash.md b/src/content/docs/reference/typescript_modules/sentrie/hash.md index 8e9145a..72cc0a5 100644 --- a/src/content/docs/reference/typescript_modules/sentrie/hash.md +++ b/src/content/docs/reference/typescript_modules/sentrie/hash.md @@ -29,7 +29,7 @@ Computes the MD5 hash of a string. ```text use { md5 } from @sentrie/hash -let hash = hash.md5("Hello, World!") // "65a8e27d8879283831b664bd8b7f0ad4" +let hash = md5("Hello, World!") // "65a8e27d8879283831b664bd8b7f0ad4" ``` ### `sha1(str: string): string` @@ -105,7 +105,7 @@ Computes HMAC (Hash-based Message Authentication Code) for data using a secret k ```text use { hmac } from @sentrie/hash -let mac = hash.hmac("sha256", "Hello, World!", "secret-key") +let mac = hmac("sha256", "Hello, World!", "secret-key") ``` ## Complete Example diff --git a/src/content/docs/running-sentrie/serving-policies.md b/src/content/docs/running-sentrie/serving-policies.md index b09d0c5..b8823fa 100644 --- a/src/content/docs/running-sentrie/serving-policies.md +++ b/src/content/docs/running-sentrie/serving-policies.md @@ -7,233 +7,15 @@ description: "How to serve policies with Sentrie via HTTP API." Policies can be served via an HTTP API using the `sentrie serve` command. This allows you to evaluate policies and rules over HTTP, making Sentrie policies accessible to other services and applications. -## Starting the Server +At a high level: -The `sentrie serve` command starts an HTTP server that exposes your policy pack for evaluation: +- **You run** `sentrie serve` to load a policy pack and start an HTTP server. +- **Callers send** JSON facts to the `/decision/{namespace}/{policy}[/{rule}]` endpoint. +- **Sentrie returns** decisions and attachments; your system enforces based on those decisions. -```bash -sentrie serve -``` +## Where to find full details -### Command Options +This page is a conceptual overview. For the complete HTTP API reference—including flags, endpoints, request/response schema, and error formats—see: -- `--port` (default: `7529`): Port number to listen on -- `--pack-location` (default: `./`): Directory containing the policy pack to serve -- `--listen` (default: `["local"]`): Address(es) to listen on. Can be specified multiple times. Accepts: - - `local` - Listen on localhost (127.0.0.1) - - `all` - Listen on all interfaces (0.0.0.0) - - Comma-separated list of specific IP addresses or hostnames - -### Examples - -```bash -# Serve the current directory on default port -sentrie serve . - -# Serve a specific pack on a custom port -sentrie serve --port 8080 /path/to/policy-pack - -# Listen on all interfaces -sentrie serve --listen all --port 8080 . - -# Listen on localhost only -sentrie serve --listen local --port 8080 . - -# Listen on multiple interfaces -sentrie serve --listen 192.168.1.100,192.168.100.101 --port 8080 . -``` - -## API Endpoints - -### Health Check - -**GET** `/health` - -Returns the health status of the server. - -**Response:** - -```json -{ - "status": "healthy", - "time": "2024-01-15T10:30:00Z" -} -``` - -### Decision Evaluation - -**POST** `/decision/{target...}` - -Evaluates a policy or rule. The `{target...}` path parameter contains the full path to the namespace, policy, and optionally the rule. - -**Path Format:** - -- `/decision/{namespace}/{policy}/{rule}` - Evaluates a specific rule within a policy -- `/decision/{namespace}/{policy}` - Evaluates all exported rules within a policy - -**Path Parameters:** - -The `{target...}` parameter is the full path after `/decision/`: - -- `{namespace}`: The namespace of the policy (e.g., `com/example/auth`) -- `{policy}`: The policy name -- `{rule}`: (Optional) The specific rule name to evaluate - -**Examples:** - -- `/decision/com/example/auth/user/isAdmin` - Evaluate the `isAdmin` rule -- `/decision/com/example/auth/user` - Evaluate all exported rules in the `user` policy - -**Request Body:** - -```json -{ - "facts": { - "user": { - "id": "123", - "role": "admin", - "permissions": ["read", "write"] - }, - "context": { - "environment": "production" - } - } -} -``` - -**Query Parameters:** - -- Query parameters are parsed and passed as run configuration (currently parsed but not used in execution) - -**Response:** - -```json -{ - "decisions": [ - { - "policy": "user", - "namespace": "com/example/auth", - "rule": "isAdmin", - "decision": { - "state": "TRUE", - "value": true - }, - "attachments": { - "role": "admin" - }, - "trace": { ... } - } - ], - "error": "" -} -``` - -**Response Fields:** - -- `decisions`: Array of decision results. Each decision contains: - - `policy`: The policy name (string) - - `namespace`: The namespace (string) - - `rule`: The rule name (string) - - `decision`: The decision result object containing: - - `state`: The decision state as a string: `"TRUE"`, `"FALSE"`, or `"UNKNOWN"` - - `value`: The actual value of the decision (any JSON-serializable type) - - `attachments`: Any attachments exported by the rule (map of string to any) - - `trace`: Execution trace information (object, optional) -- `error`: Error message if execution failed (omitted if empty/absent) - -**Decision States:** - -The `state` field in the decision object can be one of: - -- `"TRUE"`: The rule evaluated to true -- `"FALSE"`: The rule evaluated to false -- `"UNKNOWN"`: The rule evaluated to unknown (e.g., when `when` condition is false and no default is provided) - -**Example Requests:** - -```bash -# Evaluate a specific rule -curl -X POST http://localhost:7529/decision/com/example/auth/user/isAdmin \ - -H "Content-Type: application/json" \ - -d '{ - "facts": { - "user": { - "id": "123", - "role": "admin" - } - } - }' - -# Evaluate all exported rules in a policy -curl -X POST http://localhost:7529/decision/com/example/auth/user \ - -H "Content-Type: application/json" \ - -d '{ - "facts": { - "user": { - "id": "123", - "role": "admin" - } - } - }' -``` - -## Error Responses - -The API uses RFC 9457 Problem Details format for error responses. - -**Error Response Format:** - -Errors are returned using RFC 9457 Problem Details format with `Content-Type: application/problem+json`: - -```json -{ - "type": "https://sentrie.sh/problems/400", - "title": "Invalid Path", - "status": 400, - "detail": "The path parameter is required but was not provided", - "instance": "request-id-12345" -} -``` - -Additional fields may be included in the `ext` object (shown as top-level fields in JSON): - -- `timestamp`: ISO 8601 timestamp of when the error occurred - -**Common HTTP Status Codes:** - -- `200 OK`: Request successful -- `400 Bad Request`: Invalid request (e.g., malformed JSON, missing required path) -- `404 Not Found`: Policy, namespace, or rule not found -- `405 Method Not Allowed`: HTTP method not supported (only POST is allowed for decision endpoint) -- `500 Internal Server Error`: Server error during policy evaluation - -## CORS Support - -The API includes CORS headers to allow cross-origin requests: - -- `Access-Control-Allow-Origin: *` -- `Access-Control-Allow-Methods: POST, OPTIONS` -- `Access-Control-Allow-Headers: Content-Type` - -## Limitations and Future Work - -:::warning[Work in Progress] -The HTTP server implementation is still being developed. The following areas may need additional work: - -- **Request/Response Semantics**: The exact semantics of request handling, error propagation, and response formatting may be refined -- **Authentication/Authorization**: Currently, the server has no built-in authentication or authorization mechanisms -- **Rate Limiting**: No rate limiting is currently implemented -- **Request Validation**: Additional validation of request payloads may be added -- **Response Formatting**: The response format and structure may be adjusted for better consistency -- **Tracing**: The trace information in responses may be enhanced or made optional - -Please refer to the latest codebase for the most current implementation details. -::: - -## Best Practices - -1. **Use Specific Rules**: When possible, evaluate specific rules rather than entire policies for better performance and clearer results -2. **Validate Facts**: Ensure facts match the expected types and shapes defined in your policies -3. **Handle Errors**: Always check the `error` field in responses and handle error cases appropriately -4. **Use Health Checks**: Monitor the `/health` endpoint to ensure the server is running -5. **Secure Connections**: In production, use HTTPS and implement proper authentication/authorization +- [CLI Reference: `sentrie serve`](/cli-reference/serve) +- [Deployment & Operations: Running as a Service](/deployment-operations/running-as-service)